How PGSQL can facilitate the data processing workflow

Parfait Gasana

Data Analyst, Winston & Strawn
## Libraries
library(DBI)
Loading required package: methods
library(RPostgreSQL)
library(microbenchmark)

library(scales)
library(ggplot2)

options(repr.plot.width=15, repr.plot.height=5)
options(scipen=999)

Import CSV Data

csv_import <- function() {
    setwd("~/Documents/PGSQL")
    
    bus_month <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Monthly_Day-Type_Averages___Totals.csv')
    bus_rides <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Daily_Totals_by_Route.csv')

    rail_stops <- read.csv('CTA_-_System_Information_-_List_of__L__Stops.csv')
    rail_rides <- read.csv('CTA_-_Ridership_-__L__Station_Entries_-_Daily_Totals.csv')
}

Import SQL Data

sql_import <- function() {
    conn <- dbConnect(RPostgreSQL::PostgreSQL(), host="10.0.0.220", dbname="cta",
                      user="ctadba", password="cta18!", port=5432)

    bus_month <- dbGetQuery(conn, "SELECT * FROM bus_month")
    bus_rides <- dbGetQuery(conn, "SELECT * FROM bus_rides")

    rail_stops <- dbGetQuery(conn, "SELECT * FROM rail_stops")
    rail_rides <- dbGetQuery(conn, "SELECT * FROM rail_rides")

    dbDisconnect(conn)
}

summary(microbenchmark(csv_import))
summary(microbenchmark(sql_import))

View CSV Data

bus_month_csv <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Monthly_Day-Type_Averages___Totals.csv')
head(bus_month_csv)
bus_rides_csv <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Daily_Totals_by_Route.csv')
head(bus_rides_csv)
rail_stops_csv <- read.csv('CTA_-_System_Information_-_List_of__L__Stops.csv')
names(rail_stops_csv) <- tolower(names(rail_stops_csv))
head(rail_stops_csv)
rail_rides_csv <- read.csv('CTA_-_Ridership_-__L__Station_Entries_-_Daily_Totals.csv')
head(rail_rides_csv)
bus_month_csv$Month_Beginning <- as.Date(bus_month_csv$Month_Beginning, format="%m/%d/%Y", origin="1970-01-01")

bus_rides_csv$date <- as.Date(bus_rides_csv$date, format="%m/%d/%Y", origin="1970-01-01")

rail_rides_csv$date <- as.Date(rail_rides_csv$date, format="%m/%d/%Y", origin="1970-01-01")

View SQL Data

conn <- dbConnect(RPostgreSQL::PostgreSQL(), host="10.0.0.220", dbname="cta",
                  user="ctadba", password="cta18!", port=5432)

bus_month_sql <- dbGetQuery(conn, "SELECT * FROM bus_month")
head(bus_month_sql)
bus_rides_sql <- dbGetQuery(conn, "SELECT * FROM bus_rides")
head(bus_rides_sql)
rail_stations_sql <- dbGetQuery(conn, "SELECT * FROM rail_stations")
head(rail_stations_sql)
rail_rides_sql <- dbGetQuery(conn, "SELECT * FROM rail_rides")
head(rail_rides_sql)

Aggregate CSV Data

# MERGE
agg_csv <- merge(unique(bus_month_csv[c("route", "routename")]), bus_rides_csv, by="route")

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ route + routename, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(-rides.sum),])

agg_csv

Top 5 Bus Routes

# MERGE
agg_csv <- merge(subset(unique(bus_month_csv[c("route", "routename")]), 
                        routename %in% c("79th", "Ashland", "Chicago", "Western", "Cottage Grove")),
                 transform(bus_rides_csv, year=format(date, "%Y")),
                 by="route")

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ routename + year, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(routename, year),])

agg_csv

Graph CSV Data

seabornPalette <- c("#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2",
                    "#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd",
                    "#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2",
                    "#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd",
                    "#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd")

ggplot(agg_csv, aes(year, rides.sum, fill=routename)) + geom_col(position = "dodge") +
  labs(title="Top 5 CTA 'L' Bus Routes", x="Year", y="Rides") +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Aggregate SQL Data

sql <- 'SELECT rt.route_name, COUNT(rd.rides) AS "count", 
                              SUM(rd.rides) AS "sum", 
                              AVG(rd.rides) AS "mean", 
                              MEDIAN(rd.rides) AS "median",
                              MIN(rd.rides) AS "min", 
                              MAX(rd.rides) AS "max"
        FROM bus_routes rt
        INNER JOIN bus_rides rd ON rt.route_id = rd.route_id
        GROUP BY rt.route_name
        ORDER BY SUM(rd.rides) DESC'

agg_sql <- dbGetQuery(conn, sql)

agg_sql
sql <- 'SELECT rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer AS "year", 
             COUNT(rd.rides) AS "count", 
             SUM(rd.rides) AS "sum", 
             AVG(rd.rides) AS "mean", 
             MEDIAN(rd.rides) AS "median",
             MIN(rd.rides) AS "min", 
             MAX(rd.rides) AS "max"
      FROM bus_routes rt
      INNER JOIN bus_rides rd ON rt.route_id = rd.route_id
      WHERE rt.route_name IN (\'79th\', \'Ashland\', \'Chicago\', \'Western\', \'Cottage Grove\')
      GROUP BY rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer
      ORDER BY rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer'

agg_sql <- dbGetQuery(conn, sql)

agg_sql

Graph SQL Data

ggplot(agg_sql, aes(year, sum, color=route_name)) + 
  geom_line(stat="identity") + geom_point(stat="identity") +
  labs(title="Top 5 CTA 'L' Bus Routes", x="Year", y="Rides") +
  scale_x_continuous("year", breaks=unique(agg_sql$year)) +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_color_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

CSV Data Diagnostics

# TRANSFORM
agg_csv <- transform(rail_rides_csv, year=format(date, "%Y"))

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ station_id + stationname + year, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(-rides.sum),])

agg_csv
# RESHAPE
rail_lines <- c("red", "blue", "green", "brown", "purple", "purple_exp", "yellow", "pink", "orange")

rail_long <- reshape(setNames(rail_stops_csv[c("map_id", "station_name", "location", "red", "blue", "g", 
                                               "brn", "p", "pexp", "y", "pnk", "o")],
                              c("station_id", "station_name", "location", rail_lines)),
                     varying = rail_lines, v.names = "value", 
                     timevar = "rail_line", times = rail_lines,
                     new.row.names = 1:10000, direction = "long")

# SUBSET
rail_long <- unique(subset(rail_long, value=='true')[c("station_id", "station_name", "location", "rail_line")])

# ORDER
rail_long <- with(rail_long, rail_long[order(station_id, rail_line),])
row.names(rail_long) <- NULL

rail_long
merge_rail <- merge(agg_csv, rail_long, by="station_id")

merge_rail$rides.total <- merge_rail$rides.sum / with(merge_rail, ave(station_id, station_id, year, FUN=length))

merge_rail
agg_csv <- aggregate(rides.total ~ year + rail_line, merge_rail, sum)
agg_csv <- with(agg_csv, agg_csv[order(rail_line, year),])
row.names(agg_csv) <- NULL

agg_csv

SQL Data Diagnostics

sql <- 'WITH station_agg AS
         (SELECT DATE_PART(\'year\', r.ride_date)::integer AS "year",
                 r.station_id,
                 r.station_name,
                 COUNT(r.rides)::numeric(20,5) AS "count", 
                 SUM(r.rides)::numeric(20,5) AS "sum", 
                 AVG(r.rides)::numeric(20,5) AS "mean", 
                 MEDIAN(r.rides)::numeric(20,5) AS "median",
                 MIN(r.rides)::numeric(20,5) AS "min", 
                 MAX(r.rides)::numeric(20,5) AS "max"
          FROM rail_rides r
          GROUP BY DATE_PART(\'year\', r.ride_date),
                   r.station_id,
                   r.station_name
          ),
                   
      merge_rail AS
         (SELECT s.*, 
                 r.rail_line,
                 (s."sum" / COUNT(*) OVER(PARTITION BY s.station_id, "year")) AS rail_total
          FROM station_agg s
          INNER JOIN rail_stations r ON s.station_id = r.station_id
         )
         
      SELECT m."year", m.rail_line, SUM(m.rail_total)  AS rail_total
      FROM merge_rail m
      GROUP BY m."year", m.rail_line
      ORDER BY m.rail_line, m."year"'
  
agg_sql <- dbGetQuery(conn, sql)

agg_sql
cta_palette <- c(blue="#00A1DE", brown="#62361B", green="#009B3A", orange="#F9461C", pink="#E27EA6",
                 purple="#522398", purple_exp="#8059BA", red="#C60C30", yellow="#F9E300")

ggplot(subset(agg_sql, year > 2012), aes(year, rail_total, fill=rail_line)) + geom_col(position = "dodge") +
  labs(title="CTA System 'L' Lines - Total Rides By Year", x="Year", y="Rides") +
  scale_x_continuous(expand = c(0,0), "year", breaks=unique(agg_sql$year)) +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_fill_manual(values = cta_palette) + guides(fill=guide_legend("Rail Lines", nrow=1)) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Distribution

sql <- 'SELECT r.station_id, r.ride_date, r.station_name, s.rail_line, r.rides,
               (r.rides / COUNT(*) OVER(PARTITION BY s.station_id, r.ride_date)) AS rides_total
        FROM rail_rides r
        INNER JOIN rail_stations s ON s.station_id = r.station_id'

hist_long <- dbGetQuery(conn, sql)

hist_long
ggplot(hist_long, aes(x=rides_total, fill=rail_line)) +
   geom_histogram(data=subset(hist_long, rail_line == 'red'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'blue'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'brown'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'green'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'orange'), bins=100) +
   scale_x_continuous(expand = c(0, 0)) +
   scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
   scale_fill_manual(values = c(red = "#C60C30", blue = "#00A1DE", brown = "#62361B",
                                green = "#009B3A", orange = "#F9461C")) +
   labs(title="CTA Ridership Distribution By Rail Line", fill="Rail Line") +
   theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(transform(hist_long, year = format(ride_date, '%Y')), 
       aes(x=year, y=rides_total, fill=year)) + 
    geom_boxplot() + guides(fill=FALSE) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Year") +
    theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(hist_long, aes(x=rail_line, y=rides_total, fill=rail_line)) + 
    geom_boxplot() + guides(fill=FALSE) +
    scale_fill_manual(values = cta_palette) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Rail Line") +
    theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(subset(within(hist_long, { year <- format(ride_date, '%Y')
                                  year <- as.integer(as.character(year))
                                }),
              year >= 2015 & year <= 2017), 
       aes(x=factor(year), y=rides_total, fill=rail_line)) + 
    geom_boxplot() + guides(fill=guide_legend("Rail Lines", nrow=1)) +
    scale_fill_manual(values = cta_palette) +
    scale_x_discrete(expand = c(0, 0)) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Rail Line, 2015-2017", x="Year", y="Rides") +
    theme(legend.position="bottom", plot.title = element_text(hjust=0.5, size=18))

Correlation

sql <- 'WITH station_agg AS
         (SELECT DATE_PART(\'year\', r.ride_date)::integer AS "year",
                 r.station_id,
                 r.station_name,
                 COUNT(r.rides)::numeric(20,5) AS "count", 
                 SUM(r.rides)::numeric(20,5) AS "sum", 
                 AVG(r.rides)::numeric(20,5) AS "mean", 
                 MEDIAN(r.rides)::numeric(20,5) AS "median",
                 MIN(r.rides)::numeric(20,5) AS "min", 
                 MAX(r.rides)::numeric(20,5) AS "max"
          FROM rail_rides r
          GROUP BY DATE_PART(\'year\', r.ride_date),
                   r.station_id,
                   r.station_name
          ),
                   
      merge_rail AS
         (SELECT s.*, 
                 r.rail_line,
                 (s."sum" / COUNT(*) OVER(PARTITION BY s.station_id, "year")) AS rail_total
          FROM station_agg s
          INNER JOIN rail_stations r ON s.station_id = r.station_id
         )
         
      SELECT m."year",
             SUM(rail_total) FILTER (WHERE rail_line = \'blue\') AS blue,
             SUM(rail_total) FILTER (WHERE rail_line = \'brown\') AS brown,
             SUM(rail_total) FILTER (WHERE rail_line = \'green\') AS green,
             SUM(rail_total) FILTER (WHERE rail_line = \'orange\') AS orange,
             SUM(rail_total) FILTER (WHERE rail_line = \'pink\') AS pink,
             SUM(rail_total) FILTER (WHERE rail_line = \'purple\') AS purple,
             SUM(rail_total) FILTER (WHERE rail_line = \'purple_exp\') AS purple_exp,
             SUM(rail_total) FILTER (WHERE rail_line = \'red\') AS red,
             SUM(rail_total) FILTER (WHERE rail_line = \'yellow\') AS yellow
      FROM merge_rail m
      GROUP BY m."year"
      ORDER BY m."year"'

wide_sql <- dbGetQuery(conn, sql)

wide_sql
cor(wide_sql[-1], use = "complete.obs", method="pearson")
                blue     brown     green    orange      pink    purple
blue       1.0000000 0.9735257 0.8909543 0.9490871 0.8764534 0.7780920
brown      0.9735257 1.0000000 0.8839102 0.9165204 0.8888845 0.7446475
green      0.8909543 0.8839102 1.0000000 0.9295203 0.8768352 0.7684971
orange     0.9490871 0.9165204 0.9295203 1.0000000 0.8697865 0.8742380
pink       0.8764534 0.8888845 0.8768352 0.8697865 1.0000000 0.5573137
purple     0.7780920 0.7446475 0.7684971 0.8742380 0.5573137 1.0000000
purple_exp 0.9707673 0.9785738 0.9264667 0.9722522 0.8994040 0.8304641
red        0.8931410 0.8892051 0.7947539 0.9334301 0.7611228 0.9142396
yellow     0.7415022 0.7744762 0.7812455 0.7698821 0.6214899 0.7375434
           purple_exp       red    yellow
blue        0.9707673 0.8931410 0.7415022
brown       0.9785738 0.8892051 0.7744762
green       0.9264667 0.7947539 0.7812455
orange      0.9722522 0.9334301 0.7698821
pink        0.8994040 0.7611228 0.6214899
purple      0.8304641 0.9142396 0.7375434
purple_exp  1.0000000 0.9355244 0.7854706
red         0.9355244 1.0000000 0.7241108
yellow      0.7854706 0.7241108 1.0000000

T-tests

combns <- as.list(outer(rail_lines, rail_lines, function(x, y) ifelse(x==y, NA, paste(x, y))))
combns <- lapply(combns[!is.na(combns)], function(x) strsplit(x, split=" ")[[1]])

ttest_matrix <- sapply(combns, function(x){
  t <- t.test(wide_sql[[x[1]]], wide_sql[[x[2]]])
  c(statistic = t$statistic, p_value = t$p.value)
  
})

ttest_df <- data.frame(x = sapply(combns, "[", 1),
                       y = sapply(combns, "[", 2),
                       statistic = ttest_matrix[1,],
                       p_value = ttest_matrix[2,])

ttest_df <- with(ttest_df, ttest_df[order(x, y),])
ttest_df
by(ttest_df, ttest_df$x, function(sub)
  
  ggplot(sub, aes(x, statistic, fill=y)) + geom_col(position = "dodge") +
    labs(title=paste0("CTA System 'L' Lines - Pairwise T-tests for ", 
                     toupper(substr(sub$x[[1]], 1, 1)), 
                     substr(sub$x[[1]], 2, nchar(as.character(sub$x[[1]]))), " Line"), 
         x="Rail Line", y="T-test Stat") +
    scale_x_discrete(expand = c(0,0)) + 
    scale_y_continuous(expand = c(0, 0), label=comma) +
    scale_fill_manual(values = cta_palette[!names(cta_palette)==sub$x[[1]]]) + 
    guides(fill=guide_legend("Rail Lines", nrow=1)) +
    theme(legend.position="bottom",
          plot.title = element_text(hjust=0.5, size=18, colour=cta_palette[names(cta_palette)==sub$x[[1]]]),
          axis.text.x = element_text(angle=0, hjust=0.5))
  )
ttest_df$x: blue

-------------------------------------------------------- 
ttest_df$x: brown

-------------------------------------------------------- 
ttest_df$x: green

-------------------------------------------------------- 
ttest_df$x: orange

-------------------------------------------------------- 
ttest_df$x: pink

-------------------------------------------------------- 
ttest_df$x: purple

-------------------------------------------------------- 
ttest_df$x: purple_exp

-------------------------------------------------------- 
ttest_df$x: red

-------------------------------------------------------- 
ttest_df$x: yellow

CSV Regression

Right-Hand Side Variables

  • Again, requires maintenance and storage on disk space

  • Again, requires load time from non-centralized paths

  • Again, requires any ad-hoc munging for usability

# Source: St. Louis Federal Reserve Bank
unemployment_df <- read.csv('Chicago_Unemployment_Rates.csv')
unemployment_df$Date <- as.Date(unemployment_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(unemployment_df)
# Source: U.S. Department of Energy: EIA
gas_prices_df <- read.csv('US_Gas_Prices.csv')
gas_prices_df$Date <- as.Date(gas_prices_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(gas_prices_df)
# Source: U.S. National Oceanic and Atmospheric Administration (NOAA)
weather_df <- read.csv('Chicago_Weather_Data.csv')
weather_df$Date <- as.Date(weather_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(weather_df)

Bus Model Data

bus_model_data <- merge(unique(bus_month_csv[c("route")]), bus_rides_csv, by="route")

bus_model_data <- merge(bus_model_data, unemployment_df, by.x='date', by.y='Date')
bus_model_data <- merge(bus_model_data, gas_prices_df, by.x='date', by.y='Date')
bus_model_data <- merge(bus_model_data, weather_df, by.x='date', by.y='Date')

head(bus_model_data)

Add Seasons

bus_model_data$normalized_dt <- as.POSIXlt(bus_model_data$date)
bus_model_data$normalized_dt$year <- bus_model_data$normalized_dt$year +
                                        (2099 - as.integer(format(bus_model_data$date, "%Y")))
bus_model_data$normalized_dt <- as.Date(bus_model_data$normalized_dt)


bus_model_data$season <- ifelse(bus_model_data$normalized_dt >= '2099-01-01' & 
                                   bus_model_data$normalized_dt  < '2099-03-19', 'winter',
                                ifelse(bus_model_data$normalized_dt >= '2099-03-20' & 
                                          bus_model_data$normalized_dt  < '2099-06-19', 'spring',
                                       ifelse(bus_model_data$normalized_dt >= '2099-06-20' & 
                                                 bus_model_data$normalized_dt  < '2099-09-19', 'summer',
                                              ifelse(bus_model_data$normalized_dt >= '2099-09-20' & 
                                                        bus_model_data$normalized_dt  < '2099-12-19', 'fall',
                                                     ifelse(bus_model_data$normalized_dt >= '2099-12-20' & 
                                                              bus_model_data$normalized_dt  < '2099-12-31', 'winter',
                                                            NA)
                                              )
                                       )
                                )
                         )
                                       
bus_model_data[sample(nrow(bus_model_data), 10), c("normalized_dt", "date", "season")]
bus_model_data$normalized_dt <- NULL

Bus Modeling (Ordinary Least Squares)

model <- lm(rides ~ daytype + season + UE_Rate + Gas_Price + AvgTemp + Precipitation + SnowDepth,
            data = bus_model_data)

summary(model)

Call:
lm(formula = rides ~ daytype + season + UE_Rate + Gas_Price + 
    AvgTemp + Precipitation + SnowDepth, data = bus_model_data)

Residuals:
   Min     1Q Median     3Q    Max 
 -7816  -5073  -2142   3488  37902 

Coefficients:
                Estimate Std. Error t value            Pr(>|t|)    
(Intercept)    5290.4643    53.4881  98.909 <0.0000000000000002 ***
daytypeU      -1514.5231    33.9281 -44.639 <0.0000000000000002 ***
daytypeW        720.1838    25.7966  27.918 <0.0000000000000002 ***
seasonspring   -258.1290    23.6427 -10.918 <0.0000000000000002 ***
seasonsummer   -655.5754    29.3822 -22.312 <0.0000000000000002 ***
seasonwinter   -375.9811    27.6194 -13.613 <0.0000000000000002 ***
UE_Rate          99.5818     4.3064  23.124 <0.0000000000000002 ***
Gas_Price       -18.3744    10.7936  -1.702              0.0887 .  
AvgTemp          12.1592     0.7392  16.450 <0.0000000000000002 ***
Precipitation  -346.6681    24.0609 -14.408 <0.0000000000000002 ***
SnowDepth        -9.8439     5.3401  -1.843              0.0653 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 6484 on 638980 degrees of freedom
  (142589 observations deleted due to missingness)
Multiple R-squared:  0.01483,   Adjusted R-squared:  0.01482 
F-statistic:   962 on 10 and 638980 DF,  p-value: < 0.00000000000000022
graph_data <- data.frame(param = names(model$coefficients[-1]),
                         value = model$coefficients[-1],
                         row.names = NULL)

ggplot(graph_data) + geom_col(aes(x=param, y=value, fill=param), position = "dodge") +
  labs(title="CTA System Bus Regression Point Estimates", x="Parameters", y="Value") +
  guides(fill=FALSE) + ylim(-1600,1000) + 
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Rail Model Data

Assign Latitude and Longitude

rail_long$latitude <- as.numeric(gsub("\\(", "", gsub(",", "", sapply(as.character(rail_long$location), 
                                                         function(x) strsplit(x, split="\\s+")[[1]][1])))
                                )

rail_long$longitude <- as.numeric(gsub("\\)", "", sapply(as.character(rail_long$location), 
                                            function(x) strsplit(x, split="\\s+")[[1]][2]))
                                  )

rail_long[sample(nrow(rail_long), 10), c("location", "latitude", "longitude")]
rail_model_data <- merge(rail_long, rail_rides_csv, by="station_id")

rail_model_data$raw <- rail_model_data$rides

rail_model_data$rides <- with(rail_model_data, rides /
                                  ave(station_id, station_id, date, FUN=length))

rail_model_data <- merge(rail_model_data, unemployment_df, by.x='date', by.y='Date')
rail_model_data <- merge(rail_model_data, gas_prices_df, by.x='date', by.y='Date')
rail_model_data <- merge(rail_model_data, weather_df, by.x='date', by.y='Date')

head(rail_model_data, 10)

Add Seasons

rail_model_data$normalized_dt <- as.POSIXlt(rail_model_data$date)
rail_model_data$normalized_dt$year <- rail_model_data$normalized_dt$year +
                                        (2099 - as.integer(format(rail_model_data$date, "%Y")))
rail_model_data$normalized_dt <- as.Date(rail_model_data$normalized_dt)


rail_model_data$season <- ifelse(rail_model_data$normalized_dt >= '2099-01-01' & 
                                   rail_model_data$normalized_dt  < '2099-03-19', 'winter',
                                ifelse(rail_model_data$normalized_dt >= '2099-03-20' & 
                                          rail_model_data$normalized_dt  < '2099-06-19', 'spring',
                                       ifelse(rail_model_data$normalized_dt >= '2099-06-20' & 
                                                 rail_model_data$normalized_dt  < '2099-09-19', 'summer',
                                              ifelse(rail_model_data$normalized_dt >= '2099-09-20' & 
                                                        rail_model_data$normalized_dt  < '2099-12-19', 'fall',
                                                     ifelse(rail_model_data$normalized_dt >= '2099-12-20' & 
                                                              rail_model_data$normalized_dt  < '2099-12-31', 'winter',
                                                            NA)
                                              )
                                       )
                                )
                         )
                                       
rail_model_data[sample(nrow(rail_model_data), 10), c("normalized_dt", "date", "season")]
rail_model_data$normalized_dt <- NULL

Rail Modeling (Ordinary Least Squares)

model <- lm(rides ~ daytype + season + latitude + longitude + rail_line + 
                    UE_Rate + Gas_Price + AvgTemp + Precipitation + SnowDepth, 
            data = rail_model_data)

summary(model)

Call:
lm(formula = rides ~ daytype + season + latitude + longitude + 
    rail_line + UE_Rate + Gas_Price + AvgTemp + Precipitation + 
    SnowDepth, data = rail_model_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-6252.1  -933.1  -256.4   537.0 30569.2 

Coefficients:
                       Estimate  Std. Error t value             Pr(>|t|)
(Intercept)         -67697.5885   3581.3622  -18.90 < 0.0000000000000002
daytypeU              -453.5025      6.6974  -67.71 < 0.0000000000000002
daytypeW              1160.6410      5.3328  217.64 < 0.0000000000000002
seasonspring          -178.8823      5.3762  -33.27 < 0.0000000000000002
seasonsummer          -189.5721      6.6870  -28.35 < 0.0000000000000002
seasonwinter          -157.0931      6.2731  -25.04 < 0.0000000000000002
latitude             -3995.6159     36.9560 -108.12 < 0.0000000000000002
longitude            -2703.3418     44.1307  -61.26 < 0.0000000000000002
rail_linebrown       -1180.7391      7.2879 -162.01 < 0.0000000000000002
rail_linegreen       -2070.2382      6.9165 -299.32 < 0.0000000000000002
rail_lineorange      -1077.6339      8.3798 -128.60 < 0.0000000000000002
rail_linepink        -2211.9982      7.4465 -297.05 < 0.0000000000000002
rail_linepurple      -2027.7884     11.2705 -179.92 < 0.0000000000000002
rail_linepurple_exp  -1547.7298      7.7477 -199.77 < 0.0000000000000002
rail_linered          1640.2397      6.9847  234.83 < 0.0000000000000002
rail_lineyellow      -1400.7779     17.7956  -78.72 < 0.0000000000000002
UE_Rate                 -5.1186      0.9920   -5.16          0.000000247
Gas_Price              185.7348      2.4565   75.61 < 0.0000000000000002
AvgTemp                  5.0953      0.1679   30.34 < 0.0000000000000002
Precipitation          -82.5539      5.3670  -15.38 < 0.0000000000000002
SnowDepth                1.4842      1.2067    1.23                0.219
                       
(Intercept)         ***
daytypeU            ***
daytypeW            ***
seasonspring        ***
seasonsummer        ***
seasonwinter        ***
latitude            ***
longitude           ***
rail_linebrown      ***
rail_linegreen      ***
rail_lineorange     ***
rail_linepink       ***
rail_linepurple     ***
rail_linepurple_exp ***
rail_linered        ***
rail_lineyellow     ***
UE_Rate             ***
Gas_Price           ***
AvgTemp             ***
Precipitation       ***
SnowDepth              
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1850 on 1006966 degrees of freedom
  (223613 observations deleted due to missingness)
Multiple R-squared:  0.4033,    Adjusted R-squared:  0.4033 
F-statistic: 3.403e+04 on 20 and 1006966 DF,  p-value: < 0.00000000000000022
graph_data <- data.frame(param = names(model$coefficients[-1]),
                         value = model$coefficients[-1],
                         row.names = NULL)

ggplot(graph_data) + geom_col(aes(x=param, y=value, fill=param), position = "dodge") +
  labs(title="CTA System Rail Regression Point Estimates", x="Parameters", y="Value") +
  guides(fill=FALSE) + ylim(-4000, 2000) + 
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=45, vjust=0.95, hjust=0.95))

SQL Regression

Bus Modeling Data

CREATE MATERIALIZED VIEW Bus_Model_Data AS
    SELECT b.id, b.route_id, b.ride_date, b.day_type, b.rides, r.route_name, 
           CASE 
               WHEN b.normalized_date BETWEEN '2099-01-01' AND '2099-03-19' THEN 'winter'
               WHEN b.normalized_date BETWEEN '2099-03-20' AND '2099-06-19' THEN 'spring'
               WHEN b.normalized_date BETWEEN '2099-06-20' AND '2099-09-19' THEN 'summer'
               WHEN b.normalized_date BETWEEN '2099-09-20' AND '2099-12-19' THEN 'fall'
               WHEN b.normalized_date BETWEEN '2099-12-20' AND '2099-12-31' THEN 'winter'
               ELSE NULL
           END As season,
           ue.ue_rate, g.gas_price, w.avg_temp, w.precipitation, w.snow_depth
    FROM 
     (
      SELECT id, route_id, day_type, rides, ride_date, 
             ride_date + (2099 - date_part('year', ride_date)  ||' year')::interval as normalized_date
      FROM bus_rides
     ) b
    INNER JOIN bus_routes r ON b.route_id = r.route_id
    INNER JOIN unemployment_rates ue ON ue.ue_date = b.ride_date
    INNER JOIN gas_prices g ON g.gas_date = b.ride_date
    INNER JOIN weather_data w ON w.weather_date = b.ride_date
    ORDER BY b.ride_date, NULLIF(regexp_replace(b.route_id, '\D', '', 'g'), '')::int;
    
REFRESH MATERIALIZED VIEW Bus_Model_Data;
bus_model_data <- dbGetQuery(conn, "SELECT * FROM bus_model_data")

head(bus_model_data)

Bus Modeling (Ordinary Least Squares)

model <- lm(rides ~ day_type + season + ue_rate + gas_price + avg_temp + precipitation + snow_depth,
            data = bus_model_data)

summary(model)

Call:
lm(formula = rides ~ day_type + season + ue_rate + gas_price + 
    avg_temp + precipitation + snow_depth, data = bus_model_data)

Residuals:
   Min     1Q Median     3Q    Max 
 -7813  -5072  -2142   3487  37904 

Coefficients:
                Estimate Std. Error t value            Pr(>|t|)    
(Intercept)    5290.1066    52.9123  99.979 <0.0000000000000002 ***
day_typeU     -1512.5779    33.7077 -44.873 <0.0000000000000002 ***
day_typeW       719.1988    25.6034  28.090 <0.0000000000000002 ***
seasonspring   -259.2512    23.5676 -11.000 <0.0000000000000002 ***
seasonsummer   -644.0360    29.1817 -22.070 <0.0000000000000002 ***
seasonwinter   -384.3928    27.1703 -14.148 <0.0000000000000002 ***
ue_rate          99.6607     4.2720  23.329 <0.0000000000000002 ***
gas_price       -19.6097    10.7135  -1.830              0.0672 .  
avg_temp         12.1726     0.7301  16.673 <0.0000000000000002 ***
precipitation  -350.3917    23.6077 -14.842 <0.0000000000000002 ***
snow_depth       -9.6659     5.3147  -1.819              0.0690 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 6483 on 648166 degrees of freedom
  (133403 observations deleted due to missingness)
Multiple R-squared:  0.01478,   Adjusted R-squared:  0.01477 
F-statistic: 972.7 on 10 and 648166 DF,  p-value: < 0.00000000000000022
graph_data <- data.frame(param = names(model$coefficients[-1]),
                         value = model$coefficients[-1],
                         row.names = NULL)

ggplot(graph_data) + geom_col(aes(x=param, y=value, fill=param), position = "dodge") +
  labs(title="CTA System Bus Regression Point Estimates", x="Parameters", y="Value") +
  guides(fill=FALSE) + ylim(-1600, 1000) + 
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Rail Modeling Data

CREATE MATERIALIZED VIEW Rail_Model_Data AS
    SELECT r.id, r.station_id, r.station_name, r.ride_date, r.day_type, r.rides AS raw, 
          (r.rides / COUNT(*) OVER(PARTITION BY r.station_id, r.ride_date)) AS rides,
          CASE 
               WHEN r.normalized_date BETWEEN '2099-01-01' AND '2099-03-19' THEN 'winter'
               WHEN r.normalized_date BETWEEN '2099-03-20' AND '2099-06-19' THEN 'spring'
               WHEN r.normalized_date BETWEEN '2099-06-20' AND '2099-09-19' THEN 'summer'
               WHEN r.normalized_date BETWEEN '2099-09-20' AND '2099-12-19' THEN 'fall'
               WHEN r.normalized_date BETWEEN '2099-12-20' AND '2099-12-31' THEN 'winter'
               ELSE NULL
           END As season,        
           REPLACE(REPLACE((regexp_split_to_array(s.location, '\s+'))[1], ',', ''), '(', '')::numeric AS latitude,
           REPLACE((regexp_split_to_array(s.location, '\s+'))[2], ')', '')::numeric AS longitude,
           s.rail_line, s.ada, s.direction,
           ue.ue_rate, g.gas_price, w.avg_temp, w.precipitation, w.snow_depth
    FROM 
       (
        SELECT id, station_id, station_name, day_type, rides, ride_date, 
               ride_date + (2099 - date_part('year', ride_date)  ||' year')::interval as normalized_date
        FROM rail_rides
       )r
    INNER JOIN rail_stations s ON s.station_id = r.station_id
    INNER JOIN unemployment_rates ue ON ue.ue_date = r.ride_date
    INNER JOIN gas_prices g ON g.gas_date = r.ride_date
    INNER JOIN weather_data w ON w.weather_date = r.ride_date
    ORDER BY r.ride_date, r.station_id;
    
REFRESH MATERIALIZED VIEW Rail_Model_Data;
rail_model_data <- dbGetQuery(conn, "SELECT * FROM rail_model_data")

head(rail_model_data)

Rail Modeling (Ordinary Least Squares)

model <- lm(rides ~ day_type + season + latitude + longitude + rail_line + 
                    ue_rate + gas_price + avg_temp + precipitation + snow_depth, 
            data = rail_model_data)

summary(model)

Call:
lm(formula = rides ~ day_type + season + latitude + longitude + 
    rail_line + ue_rate + gas_price + avg_temp + precipitation + 
    snow_depth, data = rail_model_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-6249.5  -932.9  -256.2   536.9 30570.4 

Coefficients:
                       Estimate  Std. Error  t value             Pr(>|t|)
(Intercept)         -68149.8607   3556.3642  -19.163 < 0.0000000000000002
day_typeU             -454.4689      6.6549  -68.290 < 0.0000000000000002
day_typeW             1157.9025      5.2941  218.717 < 0.0000000000000002
seasonspring          -177.8150      5.3603  -33.172 < 0.0000000000000002
seasonsummer          -185.4726      6.6439  -27.916 < 0.0000000000000002
seasonwinter          -160.7995      6.1740  -26.044 < 0.0000000000000002
latitude             -3995.1405     36.6987 -108.863 < 0.0000000000000002
longitude            -2708.3027     43.8227  -61.801 < 0.0000000000000002
rail_linebrown       -1180.4292      7.2371 -163.109 < 0.0000000000000002
rail_linegreen       -2069.6169      6.8682 -301.334 < 0.0000000000000002
rail_lineorange      -1077.4023      8.3213 -129.476 < 0.0000000000000002
rail_linepink        -2211.5330      7.3945 -299.076 < 0.0000000000000002
rail_linepurple      -2027.3643     11.1919 -181.146 < 0.0000000000000002
rail_linepurple_exp  -1547.6652      7.6936 -201.163 < 0.0000000000000002
rail_linered          1641.3142      6.9360  236.637 < 0.0000000000000002
rail_lineyellow      -1400.4416     17.6707  -79.252 < 0.0000000000000002
ue_rate                 -5.3018      0.9843   -5.387         0.0000000718
gas_price              185.5411      2.4399   76.046 < 0.0000000000000002
avg_temp                 5.0833      0.1659   30.635 < 0.0000000000000002
precipitation          -83.1416      5.2864  -15.727 < 0.0000000000000002
snow_depth               1.6746      1.2015    1.394                0.163
                       
(Intercept)         ***
day_typeU           ***
day_typeW           ***
seasonspring        ***
seasonsummer        ***
seasonwinter        ***
latitude            ***
longitude           ***
rail_linebrown      ***
rail_linegreen      ***
rail_lineorange     ***
rail_linepink       ***
rail_linepurple     ***
rail_linepurple_exp ***
rail_linered        ***
rail_lineyellow     ***
ue_rate             ***
gas_price           ***
avg_temp            ***
precipitation       ***
snow_depth             
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1850 on 1021214 degrees of freedom
  (209365 observations deleted due to missingness)
Multiple R-squared:  0.4031,    Adjusted R-squared:  0.4031 
F-statistic: 3.449e+04 on 20 and 1021214 DF,  p-value: < 0.00000000000000022
graph_data <- data.frame(param = names(model$coefficients[-1]),
                         value = model$coefficients[-1],
                         row.names = NULL)

ggplot(graph_data) + geom_col(aes(x=param, y=value, fill=param), position = "dodge") +
  labs(title="CTA System Rail Regression Point Estimates", x="Parameters", y="Value") +
  guides(fill=FALSE) + ylim(-4000, 2000) + 
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=45, vjust=0.95, hjust=0.95))

# DISCONNECT FROM DATABASE
dbDisconnect(conn)
[1] TRUE

Conclusions




LS0tCnRpdGxlOiAiTGV2ZXJhZ2luZyBQb3N0Z3JlU1FMIGluIERhdGEgU2NpZW5jZSB3aXRoIFIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48aW1nIHNyYz0iSU1BR0VTL3Bvc3RncmVzcWxfci5wbmciIGhlaWdodD0iMjAwIiB3aWR0aD0iMjAwIi8+PC9kaXY+Cgo8ZGl2IHN0eWxlPSJmb250LXNpemU6IDI0cHg7Ij5Ib3cgUEdTUUwgY2FuIGZhY2lsaXRhdGUgdGhlIGRhdGEgcHJvY2Vzc2luZyB3b3JrZmxvdzwvZGl2PgojIyBQYXJmYWl0IEdhc2FuYSAjIwo8ZGl2IHN0eWxlPSJmb250LXNpemU6IDIwcHg7Ij5EYXRhIEFuYWx5c3QsIFdpbnN0b24gJiBTdHJhd248L2Rpdj4KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmRpdi5ibHVlIHByZSB7IGJhY2tncm91bmQtY29sb3I6ICNFQkY0RkE7IH0KLm1haW4tY29udGFpbmVyIHsKICBtYXgtd2lkdGg6IDEwMDBweDsKICBtYXJnaW4tbGVmdDogYXV0bzsKICBtYXJnaW4tcmlnaHQ6IGF1dG87Cn0KPC9zdHlsZT4KCmBgYHtyfQojIyBMaWJyYXJpZXMKbGlicmFyeShEQkkpCmxpYnJhcnkoUlBvc3RncmVTUUwpCmxpYnJhcnkobWljcm9iZW5jaG1hcmspCgpsaWJyYXJ5KHNjYWxlcykKbGlicmFyeShnZ3Bsb3QyKQoKb3B0aW9ucyhyZXByLnBsb3Qud2lkdGg9MTUsIHJlcHIucGxvdC5oZWlnaHQ9NSkKb3B0aW9ucyhzY2lwZW49OTk5KQoKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkltcG9ydCBDU1YgRGF0YTwvc3Bhbj4KCi0gIyMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPk9mdGVuIG9uZSBvZiB0aGUgbGFyZ2VzdCB0aW1lIGFuZCByZXNvdXJjZS1pbnRlbnNpdmUgc3RlcHMgZm9yIGRhdGEgYW5hbHlzdHM8L3NwYW4+ICMjIyMKLSAjIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+UmVxdWlyZXMgZGlzayBzcGFjZSBvbiBzaGFyZWQgbmV0d29yayBvciBsb2NhbCBkcml2ZXM8L3NwYW4+ICMjIyMKLSAjIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+TXVsdGlwbGUgZmlsZXMgdXN1YWxseSB1bm1hbmFnZWQgYWNyb3NzIHZlcnNpb25zIGFuZCB0ZWFtczwvc3Bhbj4gIyMjIwoKYGBge3J9CmNzdl9pbXBvcnQgPC0gZnVuY3Rpb24oKSB7CiAgICBzZXR3ZCgifi9Eb2N1bWVudHMvUEdTUUwiKQogICAgCiAgICBidXNfbW9udGggPC0gcmVhZC5jc3YoJ0NUQV8tX1JpZGVyc2hpcF8tX0J1c19Sb3V0ZXNfLV9Nb250aGx5X0RheS1UeXBlX0F2ZXJhZ2VzX19fVG90YWxzLmNzdicpCiAgICBidXNfcmlkZXMgPC0gcmVhZC5jc3YoJ0NUQV8tX1JpZGVyc2hpcF8tX0J1c19Sb3V0ZXNfLV9EYWlseV9Ub3RhbHNfYnlfUm91dGUuY3N2JykKCiAgICByYWlsX3N0b3BzIDwtIHJlYWQuY3N2KCdDVEFfLV9TeXN0ZW1fSW5mb3JtYXRpb25fLV9MaXN0X29mX19MX19TdG9wcy5jc3YnKQogICAgcmFpbF9yaWRlcyA8LSByZWFkLmNzdignQ1RBXy1fUmlkZXJzaGlwXy1fX0xfX1N0YXRpb25fRW50cmllc18tX0RhaWx5X1RvdGFscy5jc3YnKQp9CmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkltcG9ydCBTUUwgRGF0YTwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5DZW50cmFsaXplZCBkYXRhIGFjY2VzcyBsb2NhbGx5IG9yIHJlbW90ZWx5PC9zcGFuPiAjIyMKICAgIC0gIyMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UmVsYXRpb25hbCBtb2RlbCBlbnN1cmVzIG5vIHJlZHVuZGFuY3k8L3NwYW4+ICMjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlNlY3VyZWQgYW5kIHJvYnVzdCBkYXRhIHN0b3JhZ2U8L3NwYW4+ICMjIwogICAgLSAjIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5BdmFpbGFibGUgYmFja3VwIHdpdGggbm8gbmVlZCBvZiBsb2NhbCBjb21wdXRlcnM8L3NwYW4+ICMjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlRhYmxlIGFuZCBzZXQgcmVsYXRpb25zIHNlYW1sZXNzIGludGVncmF0ZXMgaW50byBkYXRhc2V0czwvc3Bhbj4gIyMjCiAgICAtICMjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPmF0b21pYyBjb2x1bW5zIGFuZCBkaXZlcnNlIHJvd3NldDwvc3Bhbj4gIyMjIwoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyfQpzcWxfaW1wb3J0IDwtIGZ1bmN0aW9uKCkgewogICAgY29ubiA8LSBkYkNvbm5lY3QoUlBvc3RncmVTUUw6OlBvc3RncmVTUUwoKSwgaG9zdD0iMTAuMC4wLjIyMCIsIGRibmFtZT0iY3RhIiwKICAgICAgICAgICAgICAgICAgICAgIHVzZXI9ImN0YWRiYSIsIHBhc3N3b3JkPSJjdGExOCEiLCBwb3J0PTU0MzIpCgogICAgYnVzX21vbnRoIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gYnVzX21vbnRoIikKICAgIGJ1c19yaWRlcyA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIGJ1c19yaWRlcyIpCgogICAgcmFpbF9zdG9wcyA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIHJhaWxfc3RvcHMiKQogICAgcmFpbF9yaWRlcyA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIHJhaWxfcmlkZXMiKQoKICAgIGRiRGlzY29ubmVjdChjb25uKQp9CgpzdW1tYXJ5KG1pY3JvYmVuY2htYXJrKGNzdl9pbXBvcnQpKQpzdW1tYXJ5KG1pY3JvYmVuY2htYXJrKHNxbF9pbXBvcnQpKQoKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5WaWV3IENTViBEYXRhPC9zcGFuPgoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5PZnRlbiByZXF1aXJlcyBhZC1ob2MgbXVuZ2luZzogcmUtZm9ybWF0dGluZyBvciBjb252ZXJzaW9uIG9mIGNvbHVtbnM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkNsZWFuaW5nIG91dCByZXBvcnRzIGFuZCBtZXRhZGF0YSBmcm9tIGFjdHVhbCBkYXRhIG9uIHRoZSBmbHk8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkFkanVzdGluZyBkaWZmZXJlbnQgbGV2ZWxzIGFuZCByZXNoYXBpbmcgZm9yIGFuYWx5dGljYWwgcHVycG9zZXM8L3NwYW4+ICMjIwoKYGBge3J9CmJ1c19tb250aF9jc3YgPC0gcmVhZC5jc3YoJ0NUQV8tX1JpZGVyc2hpcF8tX0J1c19Sb3V0ZXNfLV9Nb250aGx5X0RheS1UeXBlX0F2ZXJhZ2VzX19fVG90YWxzLmNzdicpCmhlYWQoYnVzX21vbnRoX2NzdikKCmJ1c19yaWRlc19jc3YgPC0gcmVhZC5jc3YoJ0NUQV8tX1JpZGVyc2hpcF8tX0J1c19Sb3V0ZXNfLV9EYWlseV9Ub3RhbHNfYnlfUm91dGUuY3N2JykKaGVhZChidXNfcmlkZXNfY3N2KQoKcmFpbF9zdG9wc19jc3YgPC0gcmVhZC5jc3YoJ0NUQV8tX1N5c3RlbV9JbmZvcm1hdGlvbl8tX0xpc3Rfb2ZfX0xfX1N0b3BzLmNzdicpCm5hbWVzKHJhaWxfc3RvcHNfY3N2KSA8LSB0b2xvd2VyKG5hbWVzKHJhaWxfc3RvcHNfY3N2KSkKaGVhZChyYWlsX3N0b3BzX2NzdikKCnJhaWxfcmlkZXNfY3N2IDwtIHJlYWQuY3N2KCdDVEFfLV9SaWRlcnNoaXBfLV9fTF9fU3RhdGlvbl9FbnRyaWVzXy1fRGFpbHlfVG90YWxzLmNzdicpCmhlYWQocmFpbF9yaWRlc19jc3YpCmBgYAoKYGBge3J9CmJ1c19tb250aF9jc3YkTW9udGhfQmVnaW5uaW5nIDwtIGFzLkRhdGUoYnVzX21vbnRoX2NzdiRNb250aF9CZWdpbm5pbmcsIGZvcm1hdD0iJW0vJWQvJVkiLCBvcmlnaW49IjE5NzAtMDEtMDEiKQoKYnVzX3JpZGVzX2NzdiRkYXRlIDwtIGFzLkRhdGUoYnVzX3JpZGVzX2NzdiRkYXRlLCBmb3JtYXQ9IiVtLyVkLyVZIiwgb3JpZ2luPSIxOTcwLTAxLTAxIikKCnJhaWxfcmlkZXNfY3N2JGRhdGUgPC0gYXMuRGF0ZShyYWlsX3JpZGVzX2NzdiRkYXRlLCBmb3JtYXQ9IiVtLyVkLyVZIiwgb3JpZ2luPSIxOTcwLTAxLTAxIikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+VmlldyBTUUwgRGF0YTwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5SZWxhdGlvbmFsIGRhdGFiYXNlcyBhcmUgcGxhbm5lZCBzeXN0ZW1zPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNvbnN0cmFpbnRzIGFuZCByZWZlcmVudGlhbCBpbnRlZ3JpdHkgZW5zdXJlcyBzdGFiaWxpdHkgYW5kIHJlbGF0aW9uc2hpcHM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UXVlcnkgbGFuZ3VhZ2UgcHJvdmlkZXMgZXhwcmVzc2lvbiwgcmVsaWFibGliaWxpdHkgYW5kIHZlcnNhdGlsaXR5PC9zcGFuPiAjIyMKCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0KY29ubiA8LSBkYkNvbm5lY3QoUlBvc3RncmVTUUw6OlBvc3RncmVTUUwoKSwgaG9zdD0iMTAuMC4wLjIyMCIsIGRibmFtZT0iY3RhIiwKICAgICAgICAgICAgICAgICAgdXNlcj0iY3RhZGJhIiwgcGFzc3dvcmQ9ImN0YTE4ISIsIHBvcnQ9NTQzMikKCmJ1c19tb250aF9zcWwgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUICogRlJPTSBidXNfbW9udGgiKQpoZWFkKGJ1c19tb250aF9zcWwpCgpidXNfcmlkZXNfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gYnVzX3JpZGVzIikKaGVhZChidXNfcmlkZXNfc3FsKQoKcmFpbF9zdGF0aW9uc19zcWwgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUICogRlJPTSByYWlsX3N0YXRpb25zIikKaGVhZChyYWlsX3N0YXRpb25zX3NxbCkKCnJhaWxfcmlkZXNfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gcmFpbF9yaWRlcyIpCmhlYWQocmFpbF9yaWRlc19zcWwpCgpgYGAKPC9kaXY+CgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5BZ2dyZWdhdGUgQ1NWIERhdGE8L3NwYW4+IAoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5EYXRhIHRvb2wgc3ludGF4IGNhbiBiZSBmb3JtaWRhYmxlIGZvciBuZXdjb21lcnMgb3IgcmV0dXJuaW5nIHVzZXJzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5Db21wbGV4IHByb2Nlc3NlcyByZXF1aXJlIGxvbmcgcGlwaW5nL2NoYWluaW5nIG9mIG9iamVjdHMgYW5kIG1ldGhvZHM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkxhbmd1YWdlIGxhY2tzIHNldC1iYXNlZCAoaS5lLiwgcmVsYXRpb25zLCBqb2luLCB1bmlvbikgZnJhbWV3b3JrPC9zcGFuPiAjIyMKICAKCmBgYHtyfQojIE1FUkdFCmFnZ19jc3YgPC0gbWVyZ2UodW5pcXVlKGJ1c19tb250aF9jc3ZbYygicm91dGUiLCAicm91dGVuYW1lIildKSwgYnVzX3JpZGVzX2NzdiwgYnk9InJvdXRlIikKCiMgQUdHUkVHQVRFCmFnZ19jc3YgPC0gZG8uY2FsbChkYXRhLmZyYW1lLCAKICAgICAgICAgICAgICAgICAgIGFnZ3JlZ2F0ZShyaWRlcyB+IHJvdXRlICsgcm91dGVuYW1lLCBhZ2dfY3N2LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBjKGNvdW50PWxlbmd0aCh4KSwgc3VtPXN1bSh4KSwgbWVhbj1tZWFuKHgpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhbj1tZWRpYW4oeCksIG1pbj1taW4oeCksIG1heD1tYXgoeCkpKSkKIyBPUkRFUgphZ2dfY3N2IDwtIHdpdGgoYWdnX2NzdiwgYWdnX2NzdltvcmRlcigtcmlkZXMuc3VtKSxdKQoKYWdnX2NzdgpgYGAKCgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5Ub3AgNSBCdXMgUm91dGVzPC9zcGFuPiAKCmBgYHtyfQojIE1FUkdFCmFnZ19jc3YgPC0gbWVyZ2Uoc3Vic2V0KHVuaXF1ZShidXNfbW9udGhfY3N2W2MoInJvdXRlIiwgInJvdXRlbmFtZSIpXSksIAogICAgICAgICAgICAgICAgICAgICAgICByb3V0ZW5hbWUgJWluJSBjKCI3OXRoIiwgIkFzaGxhbmQiLCAiQ2hpY2FnbyIsICJXZXN0ZXJuIiwgIkNvdHRhZ2UgR3JvdmUiKSksCiAgICAgICAgICAgICAgICAgdHJhbnNmb3JtKGJ1c19yaWRlc19jc3YsIHllYXI9Zm9ybWF0KGRhdGUsICIlWSIpKSwKICAgICAgICAgICAgICAgICBieT0icm91dGUiKQoKIyBBR0dSRUdBVEUKYWdnX2NzdiA8LSBkby5jYWxsKGRhdGEuZnJhbWUsIAogICAgICAgICAgICAgICAgICAgYWdncmVnYXRlKHJpZGVzIH4gcm91dGVuYW1lICsgeWVhciwgYWdnX2NzdiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgYyhjb3VudD1sZW5ndGgoeCksIHN1bT1zdW0oeCksIG1lYW49bWVhbih4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW49bWVkaWFuKHgpLCBtaW49bWluKHgpLCBtYXg9bWF4KHgpKSkpCiMgT1JERVIKYWdnX2NzdiA8LSB3aXRoKGFnZ19jc3YsIGFnZ19jc3Zbb3JkZXIocm91dGVuYW1lLCB5ZWFyKSxdKQoKYWdnX2NzdgpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkdyYXBoIENTViBEYXRhPC9zcGFuPiAKCmBgYHtyIGZpZzEsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CgpzZWFib3JuUGFsZXR0ZSA8LSBjKCIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsCiAgICAgICAgICAgICAgICAgICAgIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwKICAgICAgICAgICAgICAgICAgICAiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLAogICAgICAgICAgICAgICAgICAgICIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsCiAgICAgICAgICAgICAgICAgICAgIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwKICAgICAgICAgICAgICAgICAgICAiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLAogICAgICAgICAgICAgICAgICAgICIjNGM3MmIwIiwiIzU1YTg2OCIsIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiLCIjNGM3MmIwIiwiIzU1YTg2OCIsCiAgICAgICAgICAgICAgICAgICAgIiNjNDRlNTIiLCIjODE3MmIyIiwiI2NjYjk3NCIsIiM2NGI1Y2QiKQoKZ2dwbG90KGFnZ19jc3YsIGFlcyh5ZWFyLCByaWRlcy5zdW0sIGZpbGw9cm91dGVuYW1lKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBsYWJzKHRpdGxlPSJUb3AgNSBDVEEgJ0wnIEJ1cyBSb3V0ZXMiLCB4PSJZZWFyIiwgeT0iUmlkZXMiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxhYmVsPWNvbW1hKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gc2VhYm9yblBhbGV0dGUpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTAsIGhqdXN0PTAuNSkpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkFnZ3JlZ2F0ZSBTUUwgRGF0YTwvc3Bhbj4gCgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+Q2xlYXIsIGNvbXBhY3QgZGVjbGFyYXRpdmUgbGFuZ3VhZ2Ugd2l0aCBwb3J0YWJpbGl0eTwvc3Bhbj4gIyMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UHJvY2Vzc2luZyB3aXRoIHZpcnR1YWwgdGFibGVzIG9jY3VycyBiZWhpbmQgdGhlIHNjZW5lPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlNldC1iYXNlZCBmcmFtZXdvcmsgZmFjaWxpdGF0ZXMgYmxvY2t3aXNlLCB2ZWN0b3JpemVkIHByb2Nlc3M8L3NwYW4+ICMjIwoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyfQpzcWwgPC0gJ1NFTEVDVCBydC5yb3V0ZV9uYW1lLCBDT1VOVChyZC5yaWRlcykgQVMgImNvdW50IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNVTShyZC5yaWRlcykgQVMgInN1bSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBVkcocmQucmlkZXMpIEFTICJtZWFuIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1FRElBTihyZC5yaWRlcykgQVMgIm1lZGlhbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1JTihyZC5yaWRlcykgQVMgIm1pbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNQVgocmQucmlkZXMpIEFTICJtYXgiCiAgICAgICAgRlJPTSBidXNfcm91dGVzIHJ0CiAgICAgICAgSU5ORVIgSk9JTiBidXNfcmlkZXMgcmQgT04gcnQucm91dGVfaWQgPSByZC5yb3V0ZV9pZAogICAgICAgIEdST1VQIEJZIHJ0LnJvdXRlX25hbWUKICAgICAgICBPUkRFUiBCWSBTVU0ocmQucmlkZXMpIERFU0MnCgphZ2dfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKYWdnX3NxbApgYGAKCmBgYHtyfQpzcWwgPC0gJ1NFTEVDVCBydC5yb3V0ZV9uYW1lLCBEQVRFX1BBUlQoXCd5ZWFyXCcsIHJkLnJpZGVfZGF0ZSk6OmludGVnZXIgQVMgInllYXIiLCAKICAgICAgICAgICAgIENPVU5UKHJkLnJpZGVzKSBBUyAiY291bnQiLCAKICAgICAgICAgICAgIFNVTShyZC5yaWRlcykgQVMgInN1bSIsIAogICAgICAgICAgICAgQVZHKHJkLnJpZGVzKSBBUyAibWVhbiIsIAogICAgICAgICAgICAgTUVESUFOKHJkLnJpZGVzKSBBUyAibWVkaWFuIiwKICAgICAgICAgICAgIE1JTihyZC5yaWRlcykgQVMgIm1pbiIsIAogICAgICAgICAgICAgTUFYKHJkLnJpZGVzKSBBUyAibWF4IgogICAgICBGUk9NIGJ1c19yb3V0ZXMgcnQKICAgICAgSU5ORVIgSk9JTiBidXNfcmlkZXMgcmQgT04gcnQucm91dGVfaWQgPSByZC5yb3V0ZV9pZAogICAgICBXSEVSRSBydC5yb3V0ZV9uYW1lIElOIChcJzc5dGhcJywgXCdBc2hsYW5kXCcsIFwnQ2hpY2Fnb1wnLCBcJ1dlc3Rlcm5cJywgXCdDb3R0YWdlIEdyb3ZlXCcpCiAgICAgIEdST1VQIEJZIHJ0LnJvdXRlX25hbWUsIERBVEVfUEFSVChcJ3llYXJcJywgcmQucmlkZV9kYXRlKTo6aW50ZWdlcgogICAgICBPUkRFUiBCWSBydC5yb3V0ZV9uYW1lLCBEQVRFX1BBUlQoXCd5ZWFyXCcsIHJkLnJpZGVfZGF0ZSk6OmludGVnZXInCgphZ2dfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKYWdnX3NxbApgYGAKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+R3JhcGggU1FMIERhdGE8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3IgZmlnMiwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KZ2dwbG90KGFnZ19zcWwsIGFlcyh5ZWFyLCBzdW0sIGNvbG9yPXJvdXRlX25hbWUpKSArIAogIGdlb21fbGluZShzdGF0PSJpZGVudGl0eSIpICsgZ2VvbV9wb2ludChzdGF0PSJpZGVudGl0eSIpICsKICBsYWJzKHRpdGxlPSJUb3AgNSBDVEEgJ0wnIEJ1cyBSb3V0ZXMiLCB4PSJZZWFyIiwgeT0iUmlkZXMiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKCJ5ZWFyIiwgYnJlYWtzPXVuaXF1ZShhZ2dfc3FsJHllYXIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxhYmVsPWNvbW1hKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHNlYWJvcm5QYWxldHRlKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0wLCBoanVzdD0wLjUpKQpgYGAKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkNTViBEYXRhIERpYWdub3N0aWNzPC9zcGFuPiAjIwoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5JbXBlcmF0aXZlIG5hdHVyZSBvZiBwcm9jZXNzaW5nPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5EZW5zZSwgbmVzdGVkIGNhbGxzIGZvciBsYXllcmVkIHN0ZXBzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5MaW1pdGVkIHRvIGFwcGxpY2F0aW9uIGxheWVyPC9zcGFuPiAjIyMKCmBgYHtyfQojIFRSQU5TRk9STQphZ2dfY3N2IDwtIHRyYW5zZm9ybShyYWlsX3JpZGVzX2NzdiwgeWVhcj1mb3JtYXQoZGF0ZSwgIiVZIikpCgojIEFHR1JFR0FURQphZ2dfY3N2IDwtIGRvLmNhbGwoZGF0YS5mcmFtZSwgCiAgICAgICAgICAgICAgICAgICBhZ2dyZWdhdGUocmlkZXMgfiBzdGF0aW9uX2lkICsgc3RhdGlvbm5hbWUgKyB5ZWFyLCBhZ2dfY3N2LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBjKGNvdW50PWxlbmd0aCh4KSwgc3VtPXN1bSh4KSwgbWVhbj1tZWFuKHgpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhbj1tZWRpYW4oeCksIG1pbj1taW4oeCksIG1heD1tYXgoeCkpKSkKIyBPUkRFUgphZ2dfY3N2IDwtIHdpdGgoYWdnX2NzdiwgYWdnX2NzdltvcmRlcigtcmlkZXMuc3VtKSxdKQoKYWdnX2NzdgpgYGAKCgpgYGB7cn0KIyBSRVNIQVBFCnJhaWxfbGluZXMgPC0gYygicmVkIiwgImJsdWUiLCAiZ3JlZW4iLCAiYnJvd24iLCAicHVycGxlIiwgInB1cnBsZV9leHAiLCAieWVsbG93IiwgInBpbmsiLCAib3JhbmdlIikKCnJhaWxfbG9uZyA8LSByZXNoYXBlKHNldE5hbWVzKHJhaWxfc3RvcHNfY3N2W2MoIm1hcF9pZCIsICJzdGF0aW9uX25hbWUiLCAibG9jYXRpb24iLCAicmVkIiwgImJsdWUiLCAiZyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJicm4iLCAicCIsICJwZXhwIiwgInkiLCAicG5rIiwgIm8iKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoInN0YXRpb25faWQiLCAic3RhdGlvbl9uYW1lIiwgImxvY2F0aW9uIiwgcmFpbF9saW5lcykpLAogICAgICAgICAgICAgICAgICAgICB2YXJ5aW5nID0gcmFpbF9saW5lcywgdi5uYW1lcyA9ICJ2YWx1ZSIsIAogICAgICAgICAgICAgICAgICAgICB0aW1ldmFyID0gInJhaWxfbGluZSIsIHRpbWVzID0gcmFpbF9saW5lcywKICAgICAgICAgICAgICAgICAgICAgbmV3LnJvdy5uYW1lcyA9IDE6MTAwMDAsIGRpcmVjdGlvbiA9ICJsb25nIikKCiMgU1VCU0VUCnJhaWxfbG9uZyA8LSB1bmlxdWUoc3Vic2V0KHJhaWxfbG9uZywgdmFsdWU9PSd0cnVlJylbYygic3RhdGlvbl9pZCIsICJzdGF0aW9uX25hbWUiLCAibG9jYXRpb24iLCAicmFpbF9saW5lIildKQoKIyBPUkRFUgpyYWlsX2xvbmcgPC0gd2l0aChyYWlsX2xvbmcsIHJhaWxfbG9uZ1tvcmRlcihzdGF0aW9uX2lkLCByYWlsX2xpbmUpLF0pCnJvdy5uYW1lcyhyYWlsX2xvbmcpIDwtIE5VTEwKCnJhaWxfbG9uZwpgYGAKCgpgYGB7cn0KbWVyZ2VfcmFpbCA8LSBtZXJnZShhZ2dfY3N2LCByYWlsX2xvbmcsIGJ5PSJzdGF0aW9uX2lkIikKCm1lcmdlX3JhaWwkcmlkZXMudG90YWwgPC0gbWVyZ2VfcmFpbCRyaWRlcy5zdW0gLyB3aXRoKG1lcmdlX3JhaWwsIGF2ZShzdGF0aW9uX2lkLCBzdGF0aW9uX2lkLCB5ZWFyLCBGVU49bGVuZ3RoKSkKCm1lcmdlX3JhaWwKYGBgCgoKYGBge3J9CmFnZ19jc3YgPC0gYWdncmVnYXRlKHJpZGVzLnRvdGFsIH4geWVhciArIHJhaWxfbGluZSwgbWVyZ2VfcmFpbCwgc3VtKQphZ2dfY3N2IDwtIHdpdGgoYWdnX2NzdiwgYWdnX2NzdltvcmRlcihyYWlsX2xpbmUsIHllYXIpLF0pCnJvdy5uYW1lcyhhZ2dfY3N2KSA8LSBOVUxMCgphZ2dfY3N2CmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlNRTCBEYXRhIERpYWdub3N0aWNzPC9zcGFuPgoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNvbXBsZXggcHJvY2Vzc2luZyBzdGlsbCByZWFkYWJsZSBhbmQgbWFpbnRhaW5hYmxlPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNURXMgY2xlYXJseSBzaG93IHVuZGVybHlpbmcgdGFibGVzIGFuZCB2aWV3cyB3aXRob3V0IGhlbHBlciBvYmplY3RzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPldpbmRvdyBmdW5jdGlvbnMgYWxsb3cgdXNlZnVsIGlubGluZSBjYWxjdWxhdGlvbnM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+Q29uZGl0aW9uYWwgYWdncmVnYXRpb25zIGNsZWFybHkgc2hvdyByZXNoYXBlZCBkYXRhPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlNlYW1sZXNzIGNhc3RpbmcgYW5kIGNvbnZlcnNpb24gb2YgdHlwZXMgd2l0aCBgOjpgIG9wZXJhdG9yPC9zcGFuPiAjIyMKCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0Kc3FsIDwtICdXSVRIIHN0YXRpb25fYWdnIEFTCiAgICAgICAgIChTRUxFQ1QgREFURV9QQVJUKFwneWVhclwnLCByLnJpZGVfZGF0ZSk6OmludGVnZXIgQVMgInllYXIiLAogICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9pZCwKICAgICAgICAgICAgICAgICByLnN0YXRpb25fbmFtZSwKICAgICAgICAgICAgICAgICBDT1VOVChyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAiY291bnQiLCAKICAgICAgICAgICAgICAgICBTVU0oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgInN1bSIsIAogICAgICAgICAgICAgICAgIEFWRyhyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWVhbiIsIAogICAgICAgICAgICAgICAgIE1FRElBTihyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWVkaWFuIiwKICAgICAgICAgICAgICAgICBNSU4oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1pbiIsIAogICAgICAgICAgICAgICAgIE1BWChyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWF4IgogICAgICAgICAgRlJPTSByYWlsX3JpZGVzIHIKICAgICAgICAgIEdST1VQIEJZIERBVEVfUEFSVChcJ3llYXJcJywgci5yaWRlX2RhdGUpLAogICAgICAgICAgICAgICAgICAgci5zdGF0aW9uX2lkLAogICAgICAgICAgICAgICAgICAgci5zdGF0aW9uX25hbWUKICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAKICAgICAgbWVyZ2VfcmFpbCBBUwogICAgICAgICAoU0VMRUNUIHMuKiwgCiAgICAgICAgICAgICAgICAgci5yYWlsX2xpbmUsCiAgICAgICAgICAgICAgICAgKHMuInN1bSIgLyBDT1VOVCgqKSBPVkVSKFBBUlRJVElPTiBCWSBzLnN0YXRpb25faWQsICJ5ZWFyIikpIEFTIHJhaWxfdG90YWwKICAgICAgICAgIEZST00gc3RhdGlvbl9hZ2cgcwogICAgICAgICAgSU5ORVIgSk9JTiByYWlsX3N0YXRpb25zIHIgT04gcy5zdGF0aW9uX2lkID0gci5zdGF0aW9uX2lkCiAgICAgICAgICkKICAgICAgICAgCiAgICAgIFNFTEVDVCBtLiJ5ZWFyIiwgbS5yYWlsX2xpbmUsIFNVTShtLnJhaWxfdG90YWwpICBBUyByYWlsX3RvdGFsCiAgICAgIEZST00gbWVyZ2VfcmFpbCBtCiAgICAgIEdST1VQIEJZIG0uInllYXIiLCBtLnJhaWxfbGluZQogICAgICBPUkRFUiBCWSBtLnJhaWxfbGluZSwgbS4ieWVhciInCiAgCmFnZ19zcWwgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgphZ2dfc3FsCmBgYAoKCmBgYHtyIGZpZzMsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmN0YV9wYWxldHRlIDwtIGMoYmx1ZT0iIzAwQTFERSIsIGJyb3duPSIjNjIzNjFCIiwgZ3JlZW49IiMwMDlCM0EiLCBvcmFuZ2U9IiNGOTQ2MUMiLCBwaW5rPSIjRTI3RUE2IiwKICAgICAgICAgICAgICAgICBwdXJwbGU9IiM1MjIzOTgiLCBwdXJwbGVfZXhwPSIjODA1OUJBIiwgcmVkPSIjQzYwQzMwIiwgeWVsbG93PSIjRjlFMzAwIikKCmdncGxvdChzdWJzZXQoYWdnX3NxbCwgeWVhciA+IDIwMTIpLCBhZXMoeWVhciwgcmFpbF90b3RhbCwgZmlsbD1yYWlsX2xpbmUpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKwogIGxhYnModGl0bGU9IkNUQSBTeXN0ZW0gJ0wnIExpbmVzIC0gVG90YWwgUmlkZXMgQnkgWWVhciIsIHg9IlllYXIiLCB5PSJSaWRlcyIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCAieWVhciIsIGJyZWFrcz11bmlxdWUoYWdnX3NxbCR5ZWFyKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsYWJlbD1jb21tYSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGN0YV9wYWxldHRlKSArIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZCgiUmFpbCBMaW5lcyIsIG5yb3c9MSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTAsIGhqdXN0PTAuNSkpCmBgYAoKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MTsiPkRpc3RyaWJ1dGlvbjwvc3BhbgoKYGBge3J9CnNxbCA8LSAnU0VMRUNUIHIuc3RhdGlvbl9pZCwgci5yaWRlX2RhdGUsIHIuc3RhdGlvbl9uYW1lLCBzLnJhaWxfbGluZSwgci5yaWRlcywKICAgICAgICAgICAgICAgKHIucmlkZXMgLyBDT1VOVCgqKSBPVkVSKFBBUlRJVElPTiBCWSBzLnN0YXRpb25faWQsIHIucmlkZV9kYXRlKSkgQVMgcmlkZXNfdG90YWwKICAgICAgICBGUk9NIHJhaWxfcmlkZXMgcgogICAgICAgIElOTkVSIEpPSU4gcmFpbF9zdGF0aW9ucyBzIE9OIHMuc3RhdGlvbl9pZCA9IHIuc3RhdGlvbl9pZCcKCmhpc3RfbG9uZyA8LSBkYkdldFF1ZXJ5KGNvbm4sIHNxbCkKCmhpc3RfbG9uZwpgYGAKCmBgYHtyIGZpZzQsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdncGxvdChoaXN0X2xvbmcsIGFlcyh4PXJpZGVzX3RvdGFsLCBmaWxsPXJhaWxfbGluZSkpICsKICAgZ2VvbV9oaXN0b2dyYW0oZGF0YT1zdWJzZXQoaGlzdF9sb25nLCByYWlsX2xpbmUgPT0gJ3JlZCcpLCBiaW5zPTEwMCkgKwogICBnZW9tX2hpc3RvZ3JhbShkYXRhPXN1YnNldChoaXN0X2xvbmcsIHJhaWxfbGluZSA9PSAnYmx1ZScpLCBiaW5zPTEwMCkgKwogICBnZW9tX2hpc3RvZ3JhbShkYXRhPXN1YnNldChoaXN0X2xvbmcsIHJhaWxfbGluZSA9PSAnYnJvd24nKSwgYmlucz0xMDApICsKICAgZ2VvbV9oaXN0b2dyYW0oZGF0YT1zdWJzZXQoaGlzdF9sb25nLCByYWlsX2xpbmUgPT0gJ2dyZWVuJyksIGJpbnM9MTAwKSArCiAgIGdlb21faGlzdG9ncmFtKGRhdGE9c3Vic2V0KGhpc3RfbG9uZywgcmFpbF9saW5lID09ICdvcmFuZ2UnKSwgYmlucz0xMDApICsKICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbT1jKDAsNDAwMDApLCBsYWJlbD1jb21tYSkgKwogICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKHJlZCA9ICIjQzYwQzMwIiwgYmx1ZSA9ICIjMDBBMURFIiwgYnJvd24gPSAiIzYyMzYxQiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlZW4gPSAiIzAwOUIzQSIsIG9yYW5nZSA9ICIjRjk0NjFDIikpICsKICAgbGFicyh0aXRsZT0iQ1RBIFJpZGVyc2hpcCBEaXN0cmlidXRpb24gQnkgUmFpbCBMaW5lIiwgZmlsbD0iUmFpbCBMaW5lIikgKwogICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCkpCmBgYAoKYGBge3IgZmlnNSwgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KZ2dwbG90KHRyYW5zZm9ybShoaXN0X2xvbmcsIHllYXIgPSBmb3JtYXQocmlkZV9kYXRlLCAnJVknKSksIAogICAgICAgYWVzKHg9eWVhciwgeT1yaWRlc190b3RhbCwgZmlsbD15ZWFyKSkgKyAKICAgIGdlb21fYm94cGxvdCgpICsgZ3VpZGVzKGZpbGw9RkFMU0UpICsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW09YygwLDQwMDAwKSwgbGFiZWw9Y29tbWEpICsKICAgIGxhYnModGl0bGU9IkNUQSBSaWRlcnNoaXAgQm94cGxvdCBCeSBZZWFyIikgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpKQpgYGAKCmBgYHtyIGZpZzYsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdncGxvdChoaXN0X2xvbmcsIGFlcyh4PXJhaWxfbGluZSwgeT1yaWRlc190b3RhbCwgZmlsbD1yYWlsX2xpbmUpKSArIAogICAgZ2VvbV9ib3hwbG90KCkgKyBndWlkZXMoZmlsbD1GQUxTRSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3RhX3BhbGV0dGUpICsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW09YygwLDQwMDAwKSwgbGFiZWw9Y29tbWEpICsKICAgIGxhYnModGl0bGU9IkNUQSBSaWRlcnNoaXAgQm94cGxvdCBCeSBSYWlsIExpbmUiKSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCkpCmBgYAoKYGBge3IgZmlnNywgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KZ2dwbG90KHN1YnNldCh3aXRoaW4oaGlzdF9sb25nLCB7IHllYXIgPC0gZm9ybWF0KHJpZGVfZGF0ZSwgJyVZJykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXIgPC0gYXMuaW50ZWdlcihhcy5jaGFyYWN0ZXIoeWVhcikpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksCiAgICAgICAgICAgICAgeWVhciA+PSAyMDE1ICYgeWVhciA8PSAyMDE3KSwgCiAgICAgICBhZXMoeD1mYWN0b3IoeWVhciksIHk9cmlkZXNfdG90YWwsIGZpbGw9cmFpbF9saW5lKSkgKyAKICAgIGdlb21fYm94cGxvdCgpICsgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKCJSYWlsIExpbmVzIiwgbnJvdz0xKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3RhX3BhbGV0dGUpICsKICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gYygwLCAwKSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxpbT1jKDAsNDAwMDApLCBsYWJlbD1jb21tYSkgKwogICAgbGFicyh0aXRsZT0iQ1RBIFJpZGVyc2hpcCBCb3hwbG90IEJ5IFJhaWwgTGluZSwgMjAxNS0yMDE3IiwgeD0iWWVhciIsIHk9IlJpZGVzIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCkpCmBgYAoKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5Db3JyZWxhdGlvbjwvc3Bhbj4KCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0Kc3FsIDwtICdXSVRIIHN0YXRpb25fYWdnIEFTCiAgICAgICAgIChTRUxFQ1QgREFURV9QQVJUKFwneWVhclwnLCByLnJpZGVfZGF0ZSk6OmludGVnZXIgQVMgInllYXIiLAogICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9pZCwKICAgICAgICAgICAgICAgICByLnN0YXRpb25fbmFtZSwKICAgICAgICAgICAgICAgICBDT1VOVChyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAiY291bnQiLCAKICAgICAgICAgICAgICAgICBTVU0oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgInN1bSIsIAogICAgICAgICAgICAgICAgIEFWRyhyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWVhbiIsIAogICAgICAgICAgICAgICAgIE1FRElBTihyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWVkaWFuIiwKICAgICAgICAgICAgICAgICBNSU4oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1pbiIsIAogICAgICAgICAgICAgICAgIE1BWChyLnJpZGVzKTo6bnVtZXJpYygyMCw1KSBBUyAibWF4IgogICAgICAgICAgRlJPTSByYWlsX3JpZGVzIHIKICAgICAgICAgIEdST1VQIEJZIERBVEVfUEFSVChcJ3llYXJcJywgci5yaWRlX2RhdGUpLAogICAgICAgICAgICAgICAgICAgci5zdGF0aW9uX2lkLAogICAgICAgICAgICAgICAgICAgci5zdGF0aW9uX25hbWUKICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAKICAgICAgbWVyZ2VfcmFpbCBBUwogICAgICAgICAoU0VMRUNUIHMuKiwgCiAgICAgICAgICAgICAgICAgci5yYWlsX2xpbmUsCiAgICAgICAgICAgICAgICAgKHMuInN1bSIgLyBDT1VOVCgqKSBPVkVSKFBBUlRJVElPTiBCWSBzLnN0YXRpb25faWQsICJ5ZWFyIikpIEFTIHJhaWxfdG90YWwKICAgICAgICAgIEZST00gc3RhdGlvbl9hZ2cgcwogICAgICAgICAgSU5ORVIgSk9JTiByYWlsX3N0YXRpb25zIHIgT04gcy5zdGF0aW9uX2lkID0gci5zdGF0aW9uX2lkCiAgICAgICAgICkKICAgICAgICAgCiAgICAgIFNFTEVDVCBtLiJ5ZWFyIiwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwnYmx1ZVwnKSBBUyBibHVlLAogICAgICAgICAgICAgU1VNKHJhaWxfdG90YWwpIEZJTFRFUiAoV0hFUkUgcmFpbF9saW5lID0gXCdicm93blwnKSBBUyBicm93biwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwnZ3JlZW5cJykgQVMgZ3JlZW4sCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ29yYW5nZVwnKSBBUyBvcmFuZ2UsCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ3BpbmtcJykgQVMgcGluaywKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwncHVycGxlXCcpIEFTIHB1cnBsZSwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwncHVycGxlX2V4cFwnKSBBUyBwdXJwbGVfZXhwLAogICAgICAgICAgICAgU1VNKHJhaWxfdG90YWwpIEZJTFRFUiAoV0hFUkUgcmFpbF9saW5lID0gXCdyZWRcJykgQVMgcmVkLAogICAgICAgICAgICAgU1VNKHJhaWxfdG90YWwpIEZJTFRFUiAoV0hFUkUgcmFpbF9saW5lID0gXCd5ZWxsb3dcJykgQVMgeWVsbG93CiAgICAgIEZST00gbWVyZ2VfcmFpbCBtCiAgICAgIEdST1VQIEJZIG0uInllYXIiCiAgICAgIE9SREVSIEJZIG0uInllYXIiJwoKd2lkZV9zcWwgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgp3aWRlX3NxbApgYGAKPC9kaXY+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9CmNvcih3aWRlX3NxbFstMV0sIHVzZSA9ICJjb21wbGV0ZS5vYnMiLCBtZXRob2Q9InBlYXJzb24iKQpgYGAKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+VC10ZXN0czwvc3Bhbj4KCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0KY29tYm5zIDwtIGFzLmxpc3Qob3V0ZXIocmFpbF9saW5lcywgcmFpbF9saW5lcywgZnVuY3Rpb24oeCwgeSkgaWZlbHNlKHg9PXksIE5BLCBwYXN0ZSh4LCB5KSkpKQpjb21ibnMgPC0gbGFwcGx5KGNvbWJuc1shaXMubmEoY29tYm5zKV0sIGZ1bmN0aW9uKHgpIHN0cnNwbGl0KHgsIHNwbGl0PSIgIilbWzFdXSkKCnR0ZXN0X21hdHJpeCA8LSBzYXBwbHkoY29tYm5zLCBmdW5jdGlvbih4KXsKICB0IDwtIHQudGVzdCh3aWRlX3NxbFtbeFsxXV1dLCB3aWRlX3NxbFtbeFsyXV1dKQogIGMoc3RhdGlzdGljID0gdCRzdGF0aXN0aWMsIHBfdmFsdWUgPSB0JHAudmFsdWUpCiAgCn0pCgp0dGVzdF9kZiA8LSBkYXRhLmZyYW1lKHggPSBzYXBwbHkoY29tYm5zLCAiWyIsIDEpLAogICAgICAgICAgICAgICAgICAgICAgIHkgPSBzYXBwbHkoY29tYm5zLCAiWyIsIDIpLAogICAgICAgICAgICAgICAgICAgICAgIHN0YXRpc3RpYyA9IHR0ZXN0X21hdHJpeFsxLF0sCiAgICAgICAgICAgICAgICAgICAgICAgcF92YWx1ZSA9IHR0ZXN0X21hdHJpeFsyLF0pCgp0dGVzdF9kZiA8LSB3aXRoKHR0ZXN0X2RmLCB0dGVzdF9kZltvcmRlcih4LCB5KSxdKQp0dGVzdF9kZgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQoKYnkodHRlc3RfZGYsIHR0ZXN0X2RmJHgsIGZ1bmN0aW9uKHN1YikKICAKICBnZ3Bsb3Qoc3ViLCBhZXMoeCwgc3RhdGlzdGljLCBmaWxsPXkpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgbGFicyh0aXRsZT1wYXN0ZTAoIkNUQSBTeXN0ZW0gJ0wnIExpbmVzIC0gUGFpcndpc2UgVC10ZXN0cyBmb3IgIiwgCiAgICAgICAgICAgICAgICAgICAgIHRvdXBwZXIoc3Vic3RyKHN1YiR4W1sxXV0sIDEsIDEpKSwgCiAgICAgICAgICAgICAgICAgICAgIHN1YnN0cihzdWIkeFtbMV1dLCAyLCBuY2hhcihhcy5jaGFyYWN0ZXIoc3ViJHhbWzFdXSkpKSwgIiBMaW5lIiksIAogICAgICAgICB4PSJSYWlsIExpbmUiLCB5PSJULXRlc3QgU3RhdCIpICsKICAgIHNjYWxlX3hfZGlzY3JldGUoZXhwYW5kID0gYygwLDApKSArIAogICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGxhYmVsPWNvbW1hKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdGFfcGFsZXR0ZVshbmFtZXMoY3RhX3BhbGV0dGUpPT1zdWIkeFtbMV1dXSkgKyAKICAgIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZCgiUmFpbCBMaW5lcyIsIG5yb3c9MSkpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4LCBjb2xvdXI9Y3RhX3BhbGV0dGVbbmFtZXMoY3RhX3BhbGV0dGUpPT1zdWIkeFtbMV1dXSksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0wLCBoanVzdD0wLjUpKQogICkKCmBgYAo8L2Rpdj4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+Q1NWIFJlZ3Jlc3Npb248L3NwYW4+ICMjCgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5SaWdodC1IYW5kIFNpZGUgVmFyaWFibGVzPC9zcGFuPiAjIwoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5BZ2FpbiwgcmVxdWlyZXMgbWFpbnRlbmFuY2UgYW5kIHN0b3JhZ2Ugb24gZGlzayBzcGFjZTwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QWdhaW4sIHJlcXVpcmVzIGxvYWQgdGltZSBmcm9tIG5vbi1jZW50cmFsaXplZCBwYXRoczwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QWdhaW4sIHJlcXVpcmVzIGFueSBhZC1ob2MgbXVuZ2luZyBmb3IgdXNhYmlsaXR5PC9zcGFuPiAjIyMKCmBgYHtyfQoKIyBTb3VyY2U6IFN0LiBMb3VpcyBGZWRlcmFsIFJlc2VydmUgQmFuawp1bmVtcGxveW1lbnRfZGYgPC0gcmVhZC5jc3YoJ0NoaWNhZ29fVW5lbXBsb3ltZW50X1JhdGVzLmNzdicpCnVuZW1wbG95bWVudF9kZiREYXRlIDwtIGFzLkRhdGUodW5lbXBsb3ltZW50X2RmJERhdGUsIGZvcm1hdD0nJVktJW0tJWQnLCBvcmlnaW49JzE5NzAtMDEtMDEnKQpoZWFkKHVuZW1wbG95bWVudF9kZikKCiMgU291cmNlOiBVLlMuIERlcGFydG1lbnQgb2YgRW5lcmd5OiBFSUEKZ2FzX3ByaWNlc19kZiA8LSByZWFkLmNzdignVVNfR2FzX1ByaWNlcy5jc3YnKQpnYXNfcHJpY2VzX2RmJERhdGUgPC0gYXMuRGF0ZShnYXNfcHJpY2VzX2RmJERhdGUsIGZvcm1hdD0nJVktJW0tJWQnLCBvcmlnaW49JzE5NzAtMDEtMDEnKQpoZWFkKGdhc19wcmljZXNfZGYpCgojIFNvdXJjZTogVS5TLiBOYXRpb25hbCBPY2VhbmljIGFuZCBBdG1vc3BoZXJpYyBBZG1pbmlzdHJhdGlvbiAoTk9BQSkKd2VhdGhlcl9kZiA8LSByZWFkLmNzdignQ2hpY2Fnb19XZWF0aGVyX0RhdGEuY3N2JykKd2VhdGhlcl9kZiREYXRlIDwtIGFzLkRhdGUod2VhdGhlcl9kZiREYXRlLCBmb3JtYXQ9JyVZLSVtLSVkJywgb3JpZ2luPScxOTcwLTAxLTAxJykKaGVhZCh3ZWF0aGVyX2RmKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkJ1cyBNb2RlbCBEYXRhPC9zcGFuPgoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5FY2hvZXMgc2V0LWJhc2VkIGpvaW5zPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5SZXBldGl0aXZlIHNvdXJjaW5nICBvZiBzYW1lIG9iamVjdDwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+TmVzdGVkIGRlbnNlIHByb2Nlc3Npbmcgb2Ygc3RlcHM8L3NwYW4+ICMjIwoKYGBge3J9CmJ1c19tb2RlbF9kYXRhIDwtIG1lcmdlKHVuaXF1ZShidXNfbW9udGhfY3N2W2MoInJvdXRlIildKSwgYnVzX3JpZGVzX2NzdiwgYnk9InJvdXRlIikKCmJ1c19tb2RlbF9kYXRhIDwtIG1lcmdlKGJ1c19tb2RlbF9kYXRhLCB1bmVtcGxveW1lbnRfZGYsIGJ5Lng9J2RhdGUnLCBieS55PSdEYXRlJykKYnVzX21vZGVsX2RhdGEgPC0gbWVyZ2UoYnVzX21vZGVsX2RhdGEsIGdhc19wcmljZXNfZGYsIGJ5Lng9J2RhdGUnLCBieS55PSdEYXRlJykKYnVzX21vZGVsX2RhdGEgPC0gbWVyZ2UoYnVzX21vZGVsX2RhdGEsIHdlYXRoZXJfZGYsIGJ5Lng9J2RhdGUnLCBieS55PSdEYXRlJykKCmhlYWQoYnVzX21vZGVsX2RhdGEpCmBgYAoKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkFkZCBTZWFzb25zPC9zcGFuPgoKYGBge3J9CmJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPC0gYXMuUE9TSVhsdChidXNfbW9kZWxfZGF0YSRkYXRlKQpidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0JHllYXIgPC0gYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCR5ZWFyICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgyMDk5IC0gYXMuaW50ZWdlcihmb3JtYXQoYnVzX21vZGVsX2RhdGEkZGF0ZSwgIiVZIikpKQpidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0IDwtIGFzLkRhdGUoYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCkKCgpidXNfbW9kZWxfZGF0YSRzZWFzb24gPC0gaWZlbHNlKGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDEtMDEnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0wMy0xOScsICd3aW50ZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTAzLTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTA2LTE5JywgJ3NwcmluZycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTA2LTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0wOS0xOScsICdzdW1tZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDktMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0xMi0xOScsICdmYWxsJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA+PSAnMjA5OS0xMi0yMCcgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTEyLTMxJywgJ3dpbnRlcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKYnVzX21vZGVsX2RhdGFbc2FtcGxlKG5yb3coYnVzX21vZGVsX2RhdGEpLCAxMCksIGMoIm5vcm1hbGl6ZWRfZHQiLCAiZGF0ZSIsICJzZWFzb24iKV0KYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA8LSBOVUxMCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5CdXMgTW9kZWxpbmcgKE9yZGluYXJ5IExlYXN0IFNxdWFyZXMpPC9zcGFuPgoKYGBge3J9Cm1vZGVsIDwtIGxtKHJpZGVzIH4gZGF5dHlwZSArIHNlYXNvbiArIFVFX1JhdGUgKyBHYXNfUHJpY2UgKyBBdmdUZW1wICsgUHJlY2lwaXRhdGlvbiArIFNub3dEZXB0aCwKICAgICAgICAgICAgZGF0YSA9IGJ1c19tb2RlbF9kYXRhKQoKc3VtbWFyeShtb2RlbCkKYGBgCgpgYGB7ciBmaWc4LCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpncmFwaF9kYXRhIDwtIGRhdGEuZnJhbWUocGFyYW0gPSBuYW1lcyhtb2RlbCRjb2VmZmljaWVudHNbLTFdKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gbW9kZWwkY29lZmZpY2llbnRzWy0xXSwKICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IE5VTEwpCgpnZ3Bsb3QoZ3JhcGhfZGF0YSkgKyBnZW9tX2NvbChhZXMoeD1wYXJhbSwgeT12YWx1ZSwgZmlsbD1wYXJhbSksIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGxhYnModGl0bGU9IkNUQSBTeXN0ZW0gQnVzIFJlZ3Jlc3Npb24gUG9pbnQgRXN0aW1hdGVzIiwgeD0iUGFyYW1ldGVycyIsIHk9IlZhbHVlIikgKwogIGd1aWRlcyhmaWxsPUZBTFNFKSArIHlsaW0oLTE2MDAsMTAwMCkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBzZWFib3JuUGFsZXR0ZSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9MCwgaGp1c3Q9MC41KSkKYGBgCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5SYWlsIE1vZGVsIERhdGE8L3NwYW4+CgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5Bc3NpZ24gTGF0aXR1ZGUgYW5kIExvbmdpdHVkZTwvc3Bhbj4KCmBgYHtyfQpyYWlsX2xvbmckbGF0aXR1ZGUgPC0gYXMubnVtZXJpYyhnc3ViKCJcXCgiLCAiIiwgZ3N1YigiLCIsICIiLCBzYXBwbHkoYXMuY2hhcmFjdGVyKHJhaWxfbG9uZyRsb2NhdGlvbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBzdHJzcGxpdCh4LCBzcGxpdD0iXFxzKyIpW1sxXV1bMV0pKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCgpyYWlsX2xvbmckbG9uZ2l0dWRlIDwtIGFzLm51bWVyaWMoZ3N1YigiXFwpIiwgIiIsIHNhcHBseShhcy5jaGFyYWN0ZXIocmFpbF9sb25nJGxvY2F0aW9uKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgc3Ryc3BsaXQoeCwgc3BsaXQ9IlxccysiKVtbMV1dWzJdKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCnJhaWxfbG9uZ1tzYW1wbGUobnJvdyhyYWlsX2xvbmcpLCAxMCksIGMoImxvY2F0aW9uIiwgImxhdGl0dWRlIiwgImxvbmdpdHVkZSIpXQpgYGAKCmBgYHtyfQpyYWlsX21vZGVsX2RhdGEgPC0gbWVyZ2UocmFpbF9sb25nLCByYWlsX3JpZGVzX2NzdiwgYnk9InN0YXRpb25faWQiKQoKcmFpbF9tb2RlbF9kYXRhJHJhdyA8LSByYWlsX21vZGVsX2RhdGEkcmlkZXMKCnJhaWxfbW9kZWxfZGF0YSRyaWRlcyA8LSB3aXRoKHJhaWxfbW9kZWxfZGF0YSwgcmlkZXMgLwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZlKHN0YXRpb25faWQsIHN0YXRpb25faWQsIGRhdGUsIEZVTj1sZW5ndGgpKQoKcmFpbF9tb2RlbF9kYXRhIDwtIG1lcmdlKHJhaWxfbW9kZWxfZGF0YSwgdW5lbXBsb3ltZW50X2RmLCBieS54PSdkYXRlJywgYnkueT0nRGF0ZScpCnJhaWxfbW9kZWxfZGF0YSA8LSBtZXJnZShyYWlsX21vZGVsX2RhdGEsIGdhc19wcmljZXNfZGYsIGJ5Lng9J2RhdGUnLCBieS55PSdEYXRlJykKcmFpbF9tb2RlbF9kYXRhIDwtIG1lcmdlKHJhaWxfbW9kZWxfZGF0YSwgd2VhdGhlcl9kZiwgYnkueD0nZGF0ZScsIGJ5Lnk9J0RhdGUnKQoKaGVhZChyYWlsX21vZGVsX2RhdGEsIDEwKQpgYGAKCgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5BZGQgU2Vhc29uczwvc3Bhbj4KCmBgYHtyfQpyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA8LSBhcy5QT1NJWGx0KHJhaWxfbW9kZWxfZGF0YSRkYXRlKQpyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCR5ZWFyIDwtIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0JHllYXIgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKDIwOTkgLSBhcy5pbnRlZ2VyKGZvcm1hdChyYWlsX21vZGVsX2RhdGEkZGF0ZSwgIiVZIikpKQpyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA8LSBhcy5EYXRlKHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0KQoKCnJhaWxfbW9kZWxfZGF0YSRzZWFzb24gPC0gaWZlbHNlKHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTAxLTAxJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTAzLTE5JywgJ3dpbnRlcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTAzLTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0wNi0xOScsICdzcHJpbmcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDYtMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0wOS0xOScsICdzdW1tZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTA5LTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTEyLTE5JywgJ2ZhbGwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA+PSAnMjA5OS0xMi0yMCcgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCAgPCAnMjA5OS0xMi0zMScsICd3aW50ZXInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCnJhaWxfbW9kZWxfZGF0YVtzYW1wbGUobnJvdyhyYWlsX21vZGVsX2RhdGEpLCAxMCksIGMoIm5vcm1hbGl6ZWRfZHQiLCAiZGF0ZSIsICJzZWFzb24iKV0KcmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPC0gTlVMTApgYGAKCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5SYWlsIE1vZGVsaW5nIChPcmRpbmFyeSBMZWFzdCBTcXVhcmVzKTwvc3Bhbj4KCmBgYHtyfQptb2RlbCA8LSBsbShyaWRlcyB+IGRheXR5cGUgKyBzZWFzb24gKyBsYXRpdHVkZSArIGxvbmdpdHVkZSArIHJhaWxfbGluZSArIAogICAgICAgICAgICAgICAgICAgIFVFX1JhdGUgKyBHYXNfUHJpY2UgKyBBdmdUZW1wICsgUHJlY2lwaXRhdGlvbiArIFNub3dEZXB0aCwgCiAgICAgICAgICAgIGRhdGEgPSByYWlsX21vZGVsX2RhdGEpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKCmBgYHtyIGZpZzksIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdyYXBoX2RhdGEgPC0gZGF0YS5mcmFtZShwYXJhbSA9IG5hbWVzKG1vZGVsJGNvZWZmaWNpZW50c1stMV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBtb2RlbCRjb2VmZmljaWVudHNbLTFdLAogICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gTlVMTCkKCmdncGxvdChncmFwaF9kYXRhKSArIGdlb21fY29sKGFlcyh4PXBhcmFtLCB5PXZhbHVlLCBmaWxsPXBhcmFtKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicyh0aXRsZT0iQ1RBIFN5c3RlbSBSYWlsIFJlZ3Jlc3Npb24gUG9pbnQgRXN0aW1hdGVzIiwgeD0iUGFyYW1ldGVycyIsIHk9IlZhbHVlIikgKwogIGd1aWRlcyhmaWxsPUZBTFNFKSArIHlsaW0oLTQwMDAsIDIwMDApICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gc2VhYm9yblBhbGV0dGUpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0wLjk1LCBoanVzdD0wLjk1KSkKYGBgCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlNRTCBSZWdyZXNzaW9uPC9zcGFuPgoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkFkdmFuY2VkIHByZXBhcmF0aW9uIG9mIGRhdGE8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+TWF0ZXJpYWxpemVkIHZpZXcgZmFjaWxpdGF0ZXMgcmVwcm9kdWNpYmxlIHJlc2VhcmNoPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNvbXBhY3QgYW5kIHN0cmFpZ2h0Zm9yd2FyZCBkYXRhIHNvdXJjaW5nPC9zcGFuPiAjIyMKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5CdXMgTW9kZWxpbmcgRGF0YTwvc3Bhbj4KCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7c3FsLCBldmFsPUZBTFNFfQpDUkVBVEUgTUFURVJJQUxJWkVEIFZJRVcgQnVzX01vZGVsX0RhdGEgQVMKICAgIFNFTEVDVCBiLmlkLCBiLnJvdXRlX2lkLCBiLnJpZGVfZGF0ZSwgYi5kYXlfdHlwZSwgYi5yaWRlcywgci5yb3V0ZV9uYW1lLCAKICAgICAgICAgICBDQVNFIAogICAgICAgICAgICAgICBXSEVOIGIubm9ybWFsaXplZF9kYXRlIEJFVFdFRU4gJzIwOTktMDEtMDEnIEFORCAnMjA5OS0wMy0xOScgVEhFTiAnd2ludGVyJwogICAgICAgICAgICAgICBXSEVOIGIubm9ybWFsaXplZF9kYXRlIEJFVFdFRU4gJzIwOTktMDMtMjAnIEFORCAnMjA5OS0wNi0xOScgVEhFTiAnc3ByaW5nJwogICAgICAgICAgICAgICBXSEVOIGIubm9ybWFsaXplZF9kYXRlIEJFVFdFRU4gJzIwOTktMDYtMjAnIEFORCAnMjA5OS0wOS0xOScgVEhFTiAnc3VtbWVyJwogICAgICAgICAgICAgICBXSEVOIGIubm9ybWFsaXplZF9kYXRlIEJFVFdFRU4gJzIwOTktMDktMjAnIEFORCAnMjA5OS0xMi0xOScgVEhFTiAnZmFsbCcKICAgICAgICAgICAgICAgV0hFTiBiLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTEyLTIwJyBBTkQgJzIwOTktMTItMzEnIFRIRU4gJ3dpbnRlcicKICAgICAgICAgICAgICAgRUxTRSBOVUxMCiAgICAgICAgICAgRU5EIEFzIHNlYXNvbiwKICAgICAgICAgICB1ZS51ZV9yYXRlLCBnLmdhc19wcmljZSwgdy5hdmdfdGVtcCwgdy5wcmVjaXBpdGF0aW9uLCB3LnNub3dfZGVwdGgKICAgIEZST00gCiAgICAgKAogICAgICBTRUxFQ1QgaWQsIHJvdXRlX2lkLCBkYXlfdHlwZSwgcmlkZXMsIHJpZGVfZGF0ZSwgCiAgICAgICAgICAgICByaWRlX2RhdGUgKyAoMjA5OSAtIGRhdGVfcGFydCgneWVhcicsIHJpZGVfZGF0ZSkgIHx8JyB5ZWFyJyk6OmludGVydmFsIGFzIG5vcm1hbGl6ZWRfZGF0ZQogICAgICBGUk9NIGJ1c19yaWRlcwogICAgICkgYgogICAgSU5ORVIgSk9JTiBidXNfcm91dGVzIHIgT04gYi5yb3V0ZV9pZCA9IHIucm91dGVfaWQKICAgIElOTkVSIEpPSU4gdW5lbXBsb3ltZW50X3JhdGVzIHVlIE9OIHVlLnVlX2RhdGUgPSBiLnJpZGVfZGF0ZQogICAgSU5ORVIgSk9JTiBnYXNfcHJpY2VzIGcgT04gZy5nYXNfZGF0ZSA9IGIucmlkZV9kYXRlCiAgICBJTk5FUiBKT0lOIHdlYXRoZXJfZGF0YSB3IE9OIHcud2VhdGhlcl9kYXRlID0gYi5yaWRlX2RhdGUKICAgIE9SREVSIEJZIGIucmlkZV9kYXRlLCBOVUxMSUYocmVnZXhwX3JlcGxhY2UoYi5yb3V0ZV9pZCwgJ1xEJywgJycsICdnJyksICcnKTo6aW50OwogICAgClJFRlJFU0ggTUFURVJJQUxJWkVEIFZJRVcgQnVzX01vZGVsX0RhdGE7CmBgYAoKYGBge3J9CmJ1c19tb2RlbF9kYXRhIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gYnVzX21vZGVsX2RhdGEiKQoKaGVhZChidXNfbW9kZWxfZGF0YSkKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkJ1cyBNb2RlbGluZyAoT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyk8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9Cm1vZGVsIDwtIGxtKHJpZGVzIH4gZGF5X3R5cGUgKyBzZWFzb24gKyB1ZV9yYXRlICsgZ2FzX3ByaWNlICsgYXZnX3RlbXAgKyBwcmVjaXBpdGF0aW9uICsgc25vd19kZXB0aCwKICAgICAgICAgICAgZGF0YSA9IGJ1c19tb2RlbF9kYXRhKQoKc3VtbWFyeShtb2RlbCkKYGBgCjwvZGl2PgoKYGBge3IgZmlnMTAsIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdyYXBoX2RhdGEgPC0gZGF0YS5mcmFtZShwYXJhbSA9IG5hbWVzKG1vZGVsJGNvZWZmaWNpZW50c1stMV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBtb2RlbCRjb2VmZmljaWVudHNbLTFdLAogICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gTlVMTCkKCmdncGxvdChncmFwaF9kYXRhKSArIGdlb21fY29sKGFlcyh4PXBhcmFtLCB5PXZhbHVlLCBmaWxsPXBhcmFtKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicyh0aXRsZT0iQ1RBIFN5c3RlbSBCdXMgUmVncmVzc2lvbiBQb2ludCBFc3RpbWF0ZXMiLCB4PSJQYXJhbWV0ZXJzIiwgeT0iVmFsdWUiKSArCiAgZ3VpZGVzKGZpbGw9RkFMU0UpICsgeWxpbSgtMTYwMCwgMTAwMCkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBzZWFib3JuUGFsZXR0ZSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9MCwgaGp1c3Q9MC41KSkKYGBgCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlJhaWwgTW9kZWxpbmcgRGF0YTwvc3Bhbj4KCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7c3FsLCBldmFsPUZBTFNFfQpDUkVBVEUgTUFURVJJQUxJWkVEIFZJRVcgUmFpbF9Nb2RlbF9EYXRhIEFTCiAgICBTRUxFQ1Qgci5pZCwgci5zdGF0aW9uX2lkLCByLnN0YXRpb25fbmFtZSwgci5yaWRlX2RhdGUsIHIuZGF5X3R5cGUsIHIucmlkZXMgQVMgcmF3LCAKICAgICAgICAgIChyLnJpZGVzIC8gQ09VTlQoKikgT1ZFUihQQVJUSVRJT04gQlkgci5zdGF0aW9uX2lkLCByLnJpZGVfZGF0ZSkpIEFTIHJpZGVzLAogICAgICAgICAgQ0FTRSAKICAgICAgICAgICAgICAgV0hFTiByLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTAxLTAxJyBBTkQgJzIwOTktMDMtMTknIFRIRU4gJ3dpbnRlcicKICAgICAgICAgICAgICAgV0hFTiByLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTAzLTIwJyBBTkQgJzIwOTktMDYtMTknIFRIRU4gJ3NwcmluZycKICAgICAgICAgICAgICAgV0hFTiByLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTA2LTIwJyBBTkQgJzIwOTktMDktMTknIFRIRU4gJ3N1bW1lcicKICAgICAgICAgICAgICAgV0hFTiByLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTA5LTIwJyBBTkQgJzIwOTktMTItMTknIFRIRU4gJ2ZhbGwnCiAgICAgICAgICAgICAgIFdIRU4gci5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0xMi0yMCcgQU5EICcyMDk5LTEyLTMxJyBUSEVOICd3aW50ZXInCiAgICAgICAgICAgICAgIEVMU0UgTlVMTAogICAgICAgICAgIEVORCBBcyBzZWFzb24sICAgICAgICAKICAgICAgICAgICBSRVBMQUNFKFJFUExBQ0UoKHJlZ2V4cF9zcGxpdF90b19hcnJheShzLmxvY2F0aW9uLCAnXHMrJykpWzFdLCAnLCcsICcnKSwgJygnLCAnJyk6Om51bWVyaWMgQVMgbGF0aXR1ZGUsCiAgICAgICAgICAgUkVQTEFDRSgocmVnZXhwX3NwbGl0X3RvX2FycmF5KHMubG9jYXRpb24sICdccysnKSlbMl0sICcpJywgJycpOjpudW1lcmljIEFTIGxvbmdpdHVkZSwKICAgICAgICAgICBzLnJhaWxfbGluZSwgcy5hZGEsIHMuZGlyZWN0aW9uLAogICAgICAgICAgIHVlLnVlX3JhdGUsIGcuZ2FzX3ByaWNlLCB3LmF2Z190ZW1wLCB3LnByZWNpcGl0YXRpb24sIHcuc25vd19kZXB0aAogICAgRlJPTSAKICAgICAgICgKICAgICAgICBTRUxFQ1QgaWQsIHN0YXRpb25faWQsIHN0YXRpb25fbmFtZSwgZGF5X3R5cGUsIHJpZGVzLCByaWRlX2RhdGUsIAogICAgICAgICAgICAgICByaWRlX2RhdGUgKyAoMjA5OSAtIGRhdGVfcGFydCgneWVhcicsIHJpZGVfZGF0ZSkgIHx8JyB5ZWFyJyk6OmludGVydmFsIGFzIG5vcm1hbGl6ZWRfZGF0ZQogICAgICAgIEZST00gcmFpbF9yaWRlcwogICAgICAgKXIKICAgIElOTkVSIEpPSU4gcmFpbF9zdGF0aW9ucyBzIE9OIHMuc3RhdGlvbl9pZCA9IHIuc3RhdGlvbl9pZAogICAgSU5ORVIgSk9JTiB1bmVtcGxveW1lbnRfcmF0ZXMgdWUgT04gdWUudWVfZGF0ZSA9IHIucmlkZV9kYXRlCiAgICBJTk5FUiBKT0lOIGdhc19wcmljZXMgZyBPTiBnLmdhc19kYXRlID0gci5yaWRlX2RhdGUKICAgIElOTkVSIEpPSU4gd2VhdGhlcl9kYXRhIHcgT04gdy53ZWF0aGVyX2RhdGUgPSByLnJpZGVfZGF0ZQogICAgT1JERVIgQlkgci5yaWRlX2RhdGUsIHIuc3RhdGlvbl9pZDsKICAgIApSRUZSRVNIIE1BVEVSSUFMSVpFRCBWSUVXIFJhaWxfTW9kZWxfRGF0YTsKYGBgCgpgYGB7cn0KcmFpbF9tb2RlbF9kYXRhIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gcmFpbF9tb2RlbF9kYXRhIikKCmhlYWQocmFpbF9tb2RlbF9kYXRhKQpgYGAKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UmFpbCBNb2RlbGluZyAoT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyk8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9Cm1vZGVsIDwtIGxtKHJpZGVzIH4gZGF5X3R5cGUgKyBzZWFzb24gKyBsYXRpdHVkZSArIGxvbmdpdHVkZSArIHJhaWxfbGluZSArIAogICAgICAgICAgICAgICAgICAgIHVlX3JhdGUgKyBnYXNfcHJpY2UgKyBhdmdfdGVtcCArIHByZWNpcGl0YXRpb24gKyBzbm93X2RlcHRoLCAKICAgICAgICAgICAgZGF0YSA9IHJhaWxfbW9kZWxfZGF0YSkKCnN1bW1hcnkobW9kZWwpCmBgYAo8L2Rpdj4KCmBgYHtyIGZpZzExLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpncmFwaF9kYXRhIDwtIGRhdGEuZnJhbWUocGFyYW0gPSBuYW1lcyhtb2RlbCRjb2VmZmljaWVudHNbLTFdKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gbW9kZWwkY29lZmZpY2llbnRzWy0xXSwKICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IE5VTEwpCgpnZ3Bsb3QoZ3JhcGhfZGF0YSkgKyBnZW9tX2NvbChhZXMoeD1wYXJhbSwgeT12YWx1ZSwgZmlsbD1wYXJhbSksIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGxhYnModGl0bGU9IkNUQSBTeXN0ZW0gUmFpbCBSZWdyZXNzaW9uIFBvaW50IEVzdGltYXRlcyIsIHg9IlBhcmFtZXRlcnMiLCB5PSJWYWx1ZSIpICsKICBndWlkZXMoZmlsbD1GQUxTRSkgKyB5bGltKC00MDAwLCAyMDAwKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNlYWJvcm5QYWxldHRlKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgdmp1c3Q9MC45NSwgaGp1c3Q9MC45NSkpCmBgYAoKCmBgYHtyfQojIERJU0NPTk5FQ1QgRlJPTSBEQVRBQkFTRQpkYkRpc2Nvbm5lY3QoY29ubikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+Q29uY2x1c2lvbnM8L3NwYW4+ICMjCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48aW1nIHNyYz0iSU1BR0VTL3Bvc3RncmVzcWxfci5wbmciIGhlaWdodD0iMjAwIiB3aWR0aD0iMjAwIi8+PC9kaXY+CgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UG9zdGdyZXMgcHJvdmlkZXMgYSBzdGFibGUsIGNlbnRyYWxpemVkLCByZXBvc2l0b3J5IGZvciBkYXRhIHNvdXJjaW5nPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlBvc3RncmVzIG1haW50YWlucyBhbiBleHByZXNzaXZlIFNRTCBkaWFsZWN0IGZvciBkYXRhIHByb2Nlc3Npbmc8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UG9zdGdyZXMgc3VwcG9ydHMgZGF0YSBzY2llbmNlIHdpdGggdmVjdG9yaXplZCBtZXRob2RzIGFuZCByZXByb2R1Y2liaWxpdHk8L3NwYW4+ICMjIwoKPGJyLz4KPGJyLz4KPGJyLz4KCgo=